بلاک‌ها · بخش‌های صفحه

نظرِ مشتری‌ها، چرخان

پیش‌نمایش تمام‌صفحه

بخشِ نظرِ مشتری در دو ردیفِ چرخان با جهتِ مخالف، کارت‌های کوتاه با ستاره و عکسِ گوینده؛ با هاور می‌ایستد.

۱. نصب کامپوننت‌ها

این بلاک از ۴ آیتم دیگ استفاده می‌کند: avatarcontrol-sizeiconsmarquee

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/marquee.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/marquee.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/marquee.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/marquee.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

testimonials-marquee.tsx
"use client"

import { Avatar } from "@/components/ui/avatar"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Marquee, MarqueeItem } from "@/components/ui/marquee"
import { StarIcon } from "@/components/icons"
import { stockImages, type StockImageName } from "@/lib/stock-images"

const QUOTES: { text: string; name: string; role: string; photo: StockImageName }[] = [
  { text: "فاکتورها را سه برابر سریع‌تر می‌فرستیم.", name: "سارا محمدی", role: "کافه رست", photo: "personWoman2" },
  { text: "اولین ابزاری که حسابدارمان دوستش دارد.", name: "امیر رضایی", role: "فروشگاهِ بوم", photo: "personMan1" },
  { text: "گزارشِ ماهانه دیگر دو روز طول نمی‌کشد.", name: "نگار احمدی", role: "حسابدارِ آزاد", photo: "personWoman3" },
  { text: "پشتیبانی واقعاً آدم است و زود جواب می‌دهد.", name: "علی کاظمی", role: "استودیو نقش", photo: "personMan2" },
  { text: "اتصال به بانک یعنی صفر ورودِ دستی.", name: "مهسا کریمی", role: "طراحِ مستقل", photo: "personWoman1" },
]

function QuoteCard({ quote }: { quote: (typeof QUOTES)[number] }) {
  return (
    <figure className="flex w-80 flex-col gap-4 rounded-xl border border-border bg-card p-5">
      <span className="flex gap-0.5" aria-label="پنج ستاره">
        {[0, 1, 2, 3, 4].map((star) => (
          <StarIcon key={star} aria-hidden className="size-4 fill-[var(--dig-orange-400)] text-[var(--dig-orange-400)]" />
        ))}
      </span>
      <blockquote className="leading-7">{quote.text}</blockquote>
      <figcaption className="flex items-center gap-3">
        <Avatar size="sm" alt="" src={stockImages[quote.photo].src} />
        <span className="flex flex-col">
          <span className="text-sm font-semibold">{quote.name}</span>
          <span className="text-xs text-muted-foreground">{quote.role}</span>
        </span>
      </figcaption>
    </figure>
  )
}

/**
 * نظرِ مشتری‌ها در دو ردیفِ چرخان با جهتِ مخالف؛ با هاور می‌ایستد و لبه‌ها محو می‌شوند.
 * برای وقتی که نظرها کوتاه و زیادند و می‌خواهی حجمِ رضایت دیده شود، نه یک داستان.
 */
export default function TestimonialsMarqueeBlock() {
  return (
    <ControlSizeProvider size="xl" asChild>
      <section aria-labelledby="tm-title" className="flex flex-col gap-12 py-24">
        <div className="mx-auto flex max-w-2xl flex-col items-center gap-3 px-4 text-center">
          <span className="text-sm font-semibold text-brand-secondary">نظرِ مشتری‌ها</span>
          <h2 id="tm-title" className="text-3xl leading-snug font-black text-balance sm:text-4xl">
            هزاران کسب‌وکار هر روز با ما کار می‌کنند
          </h2>
        </div>
        <div className="flex flex-col gap-4">
          <Marquee fade pauseOnHover duration={45} gap={16} repeat={2}>
            {QUOTES.map((quote) => (
              <MarqueeItem key={quote.name}>
                <QuoteCard quote={quote} />
              </MarqueeItem>
            ))}
          </Marquee>
          <Marquee fade pauseOnHover direction="reverse" duration={50} gap={16} repeat={2}>
            {[...QUOTES].reverse().map((quote) => (
              <MarqueeItem key={quote.name}>
                <QuoteCard quote={quote} />
              </MarqueeItem>
            ))}
          </Marquee>
        </div>
      </section>
    </ControlSizeProvider>
  )
}