بلاک‌ها · رزرو و خدمات

صفحهٔ دوره: پخش‌کنندهٔ درسِ فعلی، مدرس با عکس و آمار، ستونِ پیشرفت و سرفصل‌ها در آکاردئون با تیکِ انجام‌شده، درسِ فعلی و زمانِ هر درس.

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

این بلاک از ۷ آیتم دیگ استفاده می‌کند: accordionavatarbuttoncontrol-sizeiconspersianprogress

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

۲. کد صفحه

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

course.tsx
"use client"

import * as React from "react"

import { CheckIcon, ClockIcon, PlayIcon, UsersIcon } from "@/components/icons"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Progress } from "@/components/ui/progress"
import { toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"

type Lesson = { title: string; minutes: number; done?: boolean; current?: boolean }
const CHAPTERS: { title: string; lessons: Lesson[] }[] = [
  {
    title: "فصلِ ۱: پایه‌های حسابداری",
    lessons: [
      { title: "دارایی، بدهی و سرمایه به زبانِ ساده", minutes: 12, done: true },
      { title: "سند و دفترِ روزنامه", minutes: 18, done: true },
      { title: "تراز آزمایشی", minutes: 15, current: true },
    ],
  },
  {
    title: "فصلِ ۲: گزارش‌های مالی",
    lessons: [
      { title: "سود و زیان", minutes: 20 },
      { title: "ترازنامه", minutes: 16 },
      { title: "جریانِ نقد", minutes: 22 },
    ],
  },
  { title: "فصلِ ۳: مالیات و بیمه", lessons: [{ title: "اظهارنامهٔ فصلی", minutes: 25 }, { title: "لیستِ بیمه", minutes: 14 }] },
]

/**
 * دورهٔ آموزشی: پخش‌کنندهٔ درسِ فعلی (عکسِ کاور با دکمهٔ پخش)، عنوان، مدرس با عکس و آمار؛
 * ستونِ کنار با پیشرفت و سرفصل‌ها در آکاردئون (انجام‌شده تیک، فعلی برجسته، زمانِ هر درس).
 */
export default function CourseBlock() {
  const lessons = CHAPTERS.flatMap((chapter) => chapter.lessons)
  const done = lessons.filter((lesson) => lesson.done).length
  const current = lessons.find((lesson) => lesson.current)!

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto grid grid-cols-1 w-full max-w-6xl items-start gap-8 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_22rem]">
        <div className="flex flex-col gap-6">
          <div className="relative overflow-hidden rounded-xl bg-inverse">
            {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
            <img src={stockImages.coverCalculator.src} alt="" className="aspect-video w-full object-cover" />
            <div className="absolute inset-0 flex items-center justify-center bg-[color-mix(in_oklab,var(--dig-graphite-950)_35%,transparent)]">
              <Button aria-label={`پخشِ درسِ «${current.title}»`} className="bg-[var(--dig-white)] text-[var(--dig-graphite-900)] hover:bg-[var(--dig-neutral-200)]">
                <PlayIcon />
                پخشِ درس
              </Button>
            </div>
          </div>
          <div className="flex flex-col gap-3">
            <span className="text-sm font-semibold text-brand-secondary">درسِ فعلی</span>
            <h1 className="text-2xl font-bold">{current.title}</h1>
            <p className="leading-8 text-muted-foreground">تراز آزمایشی را می‌سازیم و یاد می‌گیریم با آن خطاهای ثبتِ سند را قبل از بستنِ دوره پیدا کنیم.</p>
          </div>
          <div className="flex flex-wrap items-center justify-between gap-4 rounded-xl bg-subtle p-4">
            <div className="flex items-center gap-3">
              <Avatar size="lg" alt="نگار احمدی" src={stockImages.personWoman3.src} />
              <span className="flex flex-col">
                <span className="font-semibold">نگار احمدی</span>
                <span className="text-sm text-muted-foreground">حسابدارِ رسمی، ۱۲ سال تدریس</span>
              </span>
            </div>
            <ul className="flex gap-5 text-sm text-muted-foreground">
              <li className="flex items-center gap-1.5">
                <UsersIcon aria-hidden className="size-4" />
                ۳٬۴۰۰ دانشجو
              </li>
              <li className="flex items-center gap-1.5">
                <ClockIcon aria-hidden className="size-4" />
                {toFaDigits(Math.round(lessons.reduce((sum, lesson) => sum + lesson.minutes, 0) / 60))} ساعت
              </li>
            </ul>
          </div>
        </div>

        <aside className="flex flex-col gap-5 rounded-xl border border-border bg-card p-5 lg:sticky lg:top-6">
          <div className="flex flex-col gap-2">
            <div className="flex items-baseline justify-between">
              <span className="font-bold">پیشرفتِ تو</span>
              <span className="text-sm text-muted-foreground tabular-nums">
                {toFaDigits(done)} از {toFaDigits(lessons.length)} درس
              </span>
            </div>
            <Progress value={(done / lessons.length) * 100} aria-label="پیشرفتِ دوره" />
          </div>
          <Accordion type="multiple" defaultValue={[CHAPTERS[0].title]}>
            {CHAPTERS.map((chapter) => (
              <AccordionItem key={chapter.title} value={chapter.title}>
                <AccordionTrigger className="text-sm">{chapter.title}</AccordionTrigger>
                <AccordionContent>
                  <ol className="flex flex-col gap-1">
                    {chapter.lessons.map((lesson) => (
                      <li key={lesson.title}>
                        <a
                          href="#"
                          aria-current={lesson.current ? "step" : undefined}
                          className={`flex items-center gap-3 rounded-lg p-2.5 text-sm ${lesson.current ? "bg-primary-subtle font-semibold" : "hover:bg-subtle"}`}
                        >
                          <span
                            aria-hidden
                            className={`flex size-6 shrink-0 items-center justify-center rounded-full [&>svg]:size-3 ${lesson.done ? "bg-success text-[var(--dig-white)]" : lesson.current ? "bg-foreground text-background" : "border border-input"}`}
                          >
                            {lesson.done ? <CheckIcon /> : lesson.current ? <PlayIcon /> : null}
                          </span>
                          <span className="flex-1">{lesson.title}</span>
                          <span className="text-xs text-muted-foreground tabular-nums">{toFaDigits(lesson.minutes)} دقیقه</span>
                        </a>
                      </li>
                    ))}
                  </ol>
                </AccordionContent>
              </AccordionItem>
            ))}
          </Accordion>
        </aside>
      </main>
    </ControlSizeProvider>
  )
}