104 lines
5.0 KiB
TypeScript
104 lines
5.0 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { AnimatePresence, motion, PanInfo } from 'framer-motion';
|
|
import { ChevronLeftIcon, ChevronRightIcon, QuoteIcon } from 'lucide-react';
|
|
import { SectionHeading } from '../SectionHeading';
|
|
import { useSite } from '../../api';
|
|
import { useLang } from '../../i18n/lang';
|
|
import { EditableItem } from '../../admin/Editable';
|
|
|
|
export function Testimonials() {
|
|
const { testimonials } = useSite();
|
|
const { t } = useLang();
|
|
const [index, setIndex] = useState(0);
|
|
const current = testimonials[Math.min(index, Math.max(testimonials.length - 1, 0))];
|
|
if (!current) return null;
|
|
|
|
const go = (dir: number) => {
|
|
setIndex((prev) => (prev + dir + testimonials.length) % testimonials.length);
|
|
};
|
|
|
|
const handleDragEnd = (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
|
|
if (info.offset.x > 50) {
|
|
go(1); // سوایپ به راست
|
|
} else if (info.offset.x < -50) {
|
|
go(-1); // سوایپ به چپ
|
|
}
|
|
};
|
|
|
|
return (
|
|
<section className="border-y border-ivory/10 bg-char py-14 lg:py-28" aria-labelledby="testimonials-title">
|
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
|
<SectionHeading headingKey="testimonials" align="center" />
|
|
|
|
<div className="relative mx-auto mt-8 max-w-3xl text-center select-none">
|
|
<QuoteIcon className="mx-auto h-6 w-6 text-copper/60 md:h-8 md:w-8" aria-hidden="true" />
|
|
|
|
<div className="relative min-h-[170px] overflow-hidden md:min-h-[190px]">
|
|
<AnimatePresence mode="wait">
|
|
<motion.blockquote
|
|
key={current.id}
|
|
drag="x"
|
|
dragConstraints={{ left: 0, right: 0 }}
|
|
dragElastic={0.2}
|
|
onDragEnd={handleDragEnd}
|
|
initial={{ opacity: 0, y: 12 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -12 }}
|
|
transition={{ duration: 0.28, ease: [0.23, 1, 0.32, 1] }}
|
|
className="cursor-grab active:cursor-grabbing"
|
|
>
|
|
<EditableItem resource="testimonials" item={current}>
|
|
<p className="mt-5 text-base font-light leading-8 text-ivory/85 md:mt-8 md:text-xl md:leading-[2.4rem]">
|
|
«{current.quote}»
|
|
</p>
|
|
<footer className="mt-5 md:mt-8">
|
|
<p className="text-sm font-bold text-copper md:text-base">{current.name}</p>
|
|
<p className="mt-0.5 text-xs font-light text-ivory/45 md:text-sm">{current.role}</p>
|
|
</footer>
|
|
</EditableItem>
|
|
</motion.blockquote>
|
|
</AnimatePresence>
|
|
</div>
|
|
|
|
<div className="mt-6 flex items-center justify-center gap-5 md:mt-10">
|
|
<button
|
|
type="button"
|
|
onClick={() => go(1)}
|
|
aria-label={t('prevTestimonial')}
|
|
className="group flex h-10 w-10 items-center justify-center rounded-full border border-copper/35 bg-char/80 text-ivory/80 shadow-[0_4px_20px_rgba(0,0,0,0.35)] backdrop-blur-md transition-all duration-300 ease-lux hover:scale-110 hover:border-copper hover:bg-copper hover:text-char hover:shadow-[0_0_25px_rgba(197,143,92,0.45)] active:scale-95 md:h-12 md:w-12"
|
|
>
|
|
<ChevronRightIcon className="h-4 w-4 transition-transform duration-300 ease-lux ltr:rotate-180 rtl:group-hover:translate-x-0.5 ltr:group-hover:-translate-x-0.5 md:h-5 md:w-5" />
|
|
</button>
|
|
|
|
<div className="flex items-center gap-2" role="tablist" aria-label={t('selectTestimonialAria')}>
|
|
{testimonials.map((item, i) => (
|
|
<button
|
|
key={item.id}
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={i === index}
|
|
aria-label={`${t("testimonialByPrefix")} ${item.name}`}
|
|
onClick={() => setIndex(i)}
|
|
className={`h-2 rounded-full transition-all duration-300 ease-lux ${
|
|
i === index
|
|
? 'w-7 bg-copper shadow-[0_0_12px_rgba(197,143,92,0.6)] md:w-8'
|
|
: 'w-2 bg-ivory/20 hover:bg-ivory/40 md:w-2.5'
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => go(-1)}
|
|
aria-label={t('nextTestimonial')}
|
|
className="group flex h-10 w-10 items-center justify-center rounded-full border border-copper/35 bg-char/80 text-ivory/80 shadow-[0_4px_20px_rgba(0,0,0,0.35)] backdrop-blur-md transition-all duration-300 ease-lux hover:scale-110 hover:border-copper hover:bg-copper hover:text-char hover:shadow-[0_0_25px_rgba(197,143,92,0.45)] active:scale-95 md:h-12 md:w-12"
|
|
>
|
|
<ChevronLeftIcon className="h-4 w-4 transition-transform duration-300 ease-lux ltr:rotate-180 rtl:group-hover:-translate-x-0.5 ltr:group-hover:translate-x-0.5 md:h-5 md:w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
} |