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

مراحلِ کار، آکاردئونی

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

بخشِ روندِ کار: مراحلِ شماره‌دار که هر بار یکی باز است با توضیح و اقدام، و عکسِ همان مرحله کنارش با محوشدن عوض می‌شود.

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

این بلاک از ۵ آیتم دیگ استفاده می‌کند: accordionbuttoncontrol-sizeiconspersian

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/accordion.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
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.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
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/accordion.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
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/accordion.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

۲. کد صفحه

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

process-accordion.tsx
"use client"

import * as React from "react"

import { ArrowUpIcon } from "@/components/icons"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { toFaDigits } from "@/lib/persian"
import { cn } from "@/lib/utils"
import { stockImages, type StockImageName } from "@/lib/stock-images"

const STEPS: { title: string; text: string; action: string; photo: StockImageName }[] = [
  {
    title: "بازدید از محل",
    text: "کارشناسِ ما نور، جهتِ ساختمان و فضای بام را می‌سنجد و همان روز گزارش می‌دهد.",
    action: "گزارشِ نمونه را ببینید",
    photo: "coverMinimalRoom",
  },
  {
    title: "طراحیِ سامانه",
    text: "بر اساسِ مصرفِ قبض‌های یک سالِ اخیر، اندازهٔ پنل و باتری را دقیق حساب می‌کنیم.",
    action: "ماشین‌حسابِ مصرف",
    photo: "projectDashboard",
  },
  {
    title: "مجوز و قرارداد",
    text: "همهٔ کارهای اداری با توزیعِ برق و شهرداری را خودمان پیگیری می‌کنیم.",
    action: "مدارکِ لازم",
    photo: "coverOfficeSupplies",
  },
  {
    title: "نصب در یک روز",
    text: "تیمِ نصب صبح می‌رسد و عصر سامانه به شبکه وصل است؛ بدونِ قطعیِ طولانی.",
    action: "تقویمِ نصب",
    photo: "studioSunlit",
  },
  {
    title: "پایش و نگه‌داری",
    text: "تولیدِ هر پنل را در اپ می‌بینید و اگر افت کند، پیش از شما خبردار می‌شویم.",
    action: "اپ را ببینید",
    photo: "projectMobileApp",
  },
]

/**
 * مراحلِ کار به‌شکلِ آکاردئونِ شماره‌دار: هر بار یک مرحله باز است با توضیح و اقدام، و عکسِ
 * همان مرحله کنارش با محوشدن عوض می‌شود؛ در موبایل عکس داخلِ همان مرحله می‌آید.
 */
export default function ProcessAccordionBlock() {
  const [open, setOpen] = React.useState(0)

  return (
    <ControlSizeProvider size="xl" asChild>
      <section aria-labelledby="pa-title" className="mx-auto flex w-full max-w-6xl flex-col gap-12 px-4 py-24 sm:px-6">
        <div className="flex max-w-2xl flex-col gap-3">
          <span className="text-sm font-semibold text-brand-secondary">روندِ کار</span>
          <h2 id="pa-title" className="text-3xl leading-snug font-black text-balance sm:text-4xl">
            از اولین تماس تا اولین کیلووات، پنج قدم
          </h2>
        </div>
        <div className="grid grid-cols-1 items-start gap-8 lg:grid-cols-2">
          <Accordion
            type="single"
            variant="light"
            hideIndicator
            value={String(open)}
            onValueChange={(value) => value && setOpen(Number(value))}
            className="flex flex-col gap-2"
          >
            {STEPS.map((step, index) => {
              const expanded = open === index
              return (
                <AccordionItem
                  key={step.title}
                  value={String(index)}
                  className={cn("rounded-xl border-0 px-4 transition-colors duration-300 sm:px-5", expanded ? "bg-subtle" : "hover:bg-muted")}
                >
                  <AccordionTrigger className="gap-4 py-4 text-lg font-bold hover:no-underline sm:py-5">
                    <span className="flex items-center gap-4">
                      <span
                        aria-hidden
                        className={cn(
                          "flex size-9 shrink-0 items-center justify-center rounded-full text-sm tabular-nums transition-colors",
                          expanded ? "bg-foreground text-background" : "bg-muted text-muted-foreground"
                        )}
                      >
                        {toFaDigits(index + 1)}
                      </span>
                      {step.title}
                    </span>
                  </AccordionTrigger>
                  <AccordionContent>
                    <div className="flex flex-col items-start gap-4 pb-1 ps-[3.25rem]">
                      <p className="leading-7 text-muted-foreground">{step.text}</p>
                      <Button variant="secondary">
                        {step.action}
                        <ArrowUpIcon aria-hidden className="-rotate-45" />
                      </Button>
                      {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                      <img src={stockImages[step.photo].src} alt="" className="aspect-[16/10] w-full rounded-lg object-cover lg:hidden" />
                    </div>
                  </AccordionContent>
                </AccordionItem>
              )
            })}
          </Accordion>
          <div className="sticky top-6 hidden aspect-[4/3] overflow-hidden rounded-xl bg-muted lg:block">
            {STEPS.map((step, index) => (
              // eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ
              <img
                key={step.title}
                src={stockImages[step.photo].src}
                alt={index === open ? stockImages[step.photo].alt : ""}
                aria-hidden={index !== open}
                className={cn("absolute inset-0 size-full object-cover transition-[opacity,scale] duration-700", index === open ? "scale-100" : "scale-105 [opacity:0]")}
              />
            ))}
            <span className="absolute start-4 top-4 rounded-full bg-card px-3 py-1.5 text-sm font-semibold shadow-sm">
              قدمِ {toFaDigits(open + 1)} از {toFaDigits(STEPS.length)}
            </span>
          </div>
        </div>
      </section>
    </ControlSizeProvider>
  )
}