بلاک‌ها · معرفی محصول

صفحهٔ نمونه‌کار: سرِ صفحه با آمار، فیلترِ نوعِ پروژه با سگمنت و گریدِ نامتقارن از پروژه‌ها با عکسِ بزرگ، مشتری، سال و برچسب.

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

این بلاک از ۷ آیتم دیگ استفاده می‌کند: badgebuttoncontrol-sizeiconspersianrevealtoggle-group

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

۲. کد صفحه

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

portfolio.tsx
"use client"

import * as React from "react"

import { ArrowUpIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Reveal } from "@/components/ui/reveal"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

type Project = { title: string; client: string; kind: string; year: number; image: StockImageName; tags: string[]; wide?: boolean }

const PROJECTS: Project[] = [
  { title: "اپِ بانکی نو", client: "بانکِ آینده", kind: "اپ موبایل", year: 1404, image: "projectMobileApp", tags: ["UX", "طراحی رابط"], wide: true },
  { title: "داشبوردِ فروش", client: "فروشگاهِ بوم", kind: "وب‌اپ", year: 1404, image: "projectDashboard", tags: ["داده", "طراحی رابط"] },
  { title: "هویتِ بصری کافه", client: "کافه رست", kind: "برند", year: 1403, image: "projectCafeBrand", tags: ["لوگو", "بسته‌بندی"] },
  { title: "سایتِ معماری", client: "استودیو پی", kind: "وب‌سایت", year: 1403, image: "studioSunlit", tags: ["وب", "عکاسی"], wide: true },
  { title: "کاتالوگِ سفال", client: "خانهٔ سفال", kind: "برند", year: 1402, image: "productVase", tags: ["چاپ", "عکاسی"] },
  { title: "پنلِ حسابداری", client: "آوند", kind: "وب‌اپ", year: 1402, image: "coverFinanceDesk", tags: ["UX", "داده"] },
]
const KINDS = ["همه", "اپ موبایل", "وب‌اپ", "وب‌سایت", "برند"]

/**
 * نمونه‌کار: سرِ صفحه با آمارِ کوتاه، فیلترِ نوعِ پروژه با سگمنت و گریدِ نامتقارن که
 * بعضی پروژه‌ها دو ستون می‌گیرند؛ هر کارت عکسِ بزرگ، نامِ مشتری، سال و برچسب دارد.
 */
export default function PortfolioBlock() {
  const [kind, setKind] = React.useState("همه")
  const visible = PROJECTS.filter((project) => kind === "همه" || project.kind === kind)

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-6xl flex-col gap-12 px-4 py-16 sm:px-6">
        <header className="flex flex-wrap items-end justify-between gap-8">
          <div className="flex max-w-2xl flex-col gap-4">
            <h1 className="text-4xl leading-snug font-black text-balance sm:text-5xl sm:leading-snug">کارهایی که دوستشان داریم</h1>
            <p className="text-lg leading-8 text-muted-foreground">منتخبی از ۱۲۰ پروژه برای استارتاپ‌ها، بانک‌ها و برندهای کوچک.</p>
          </div>
          <dl className="flex gap-10">
            {[
              { value: "۱۲۰+", label: "پروژه" },
              { value: "۹", label: "سال تجربه" },
            ].map((stat) => (
              <div key={stat.label} className="flex flex-col">
                <dd className="text-3xl font-black tabular-nums">{stat.value}</dd>
                <dt className="text-sm text-muted-foreground">{stat.label}</dt>
              </div>
            ))}
          </dl>
        </header>

        <div className="max-w-full overflow-x-auto">
          <ToggleGroup type="single" variant="segmented" value={kind} onValueChange={(value) => value && setKind(value as string)} aria-label="نوعِ پروژه">
            {KINDS.map((item) => (
              <ToggleGroupItem key={item} value={item} className="whitespace-nowrap">
                {item}
              </ToggleGroupItem>
            ))}
          </ToggleGroup>
        </div>

        <ul className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
          {visible.map((project, index) => (
            <Reveal asChild key={project.title} delay={(index % 3) * 100}>
              <li className={project.wide && kind === "همه" ? "lg:col-span-2" : ""}>
                <a href="#" className="group flex flex-col gap-4">
                  <span className="relative overflow-hidden rounded-xl">
                    {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                    <img src={stockImages[project.image].src} alt={stockImages[project.image].alt} loading="lazy" className="aspect-[4/3] w-full object-cover transition-transform duration-500 group-hover:scale-105 lg:aspect-auto lg:h-80" />
                    <span aria-hidden className="absolute end-4 top-4 flex size-10 items-center justify-center rounded-full bg-card text-foreground shadow-card transition-transform group-hover:-translate-y-0.5 [&>svg]:size-4">
                      <ArrowUpIcon className="-rotate-45" />
                    </span>
                  </span>
                  <span className="flex items-start justify-between gap-3">
                    <span className="flex flex-col gap-1">
                      <span className="text-lg font-bold">{project.title}</span>
                      <span className="text-sm text-muted-foreground">
                        {project.client} · {toFaDigits(project.year)}
                      </span>
                    </span>
                    <span className="flex flex-wrap justify-end gap-1.5">
                      {project.tags.map((tag) => (
                        <Badge key={tag} variant="soft">
                          {tag}
                        </Badge>
                      ))}
                    </span>
                  </span>
                </a>
              </li>
            </Reveal>
          ))}
        </ul>

        <div className="flex justify-center pt-4">
          <Button variant="outline">پروژه‌های بیشتر</Button>
        </div>
      </main>
    </ControlSizeProvider>
  )
}