بلاک‌ها · موبایل

روزنامهٔ موبایل

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

نامِ نشریه درشت، روزهای هفته با دایرهٔ تاریخ، خبرِ تمام‌عرض با نوارِ استوری و خبرِ بعدی، پربازدیدها با خلاصه و ناوبریِ پایین.

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

این بلاک از ۶ آیتم دیگ استفاده می‌کند: bottom-navigationbuttoncontrol-sizeiconspersiantoggle-group

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

۲. کد صفحه

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

mobile-news-editorial.tsx
"use client"

import * as React from "react"

import { BellIcon, DashboardIcon, GlobeIcon, HeartIcon, UserIcon } from "@/components/icons"
import { BottomNavigation, BottomNavigationItem } from "@/components/ui/bottom-navigation"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

const DAYS = [
  { value: "14", day: 14, name: "دوشنبه" },
  { value: "15", day: 15, name: "سه‌شنبه" },
  { value: "16", day: 16, name: "چهارشنبه" },
  { value: "17", day: 17, name: "پنجشنبه" },
  { value: "18", day: 18, name: "جمعه" },
]
const STORIES: { id: string; title: string; time: string; image: StockImageName }[] = [
  { id: "s1", title: "طرحِ تازهٔ حمل‌ونقلِ عمومی؛ هفت شهر خطِ اتوبوسِ برقی می‌گیرند", time: "۱۸ مهر · ۱۵:۳۰", image: "planeWindow" },
  { id: "s2", title: "کتاب‌خوانی در کافه‌ها دوباره رونق گرفته است", time: "۱۸ مهر · ۱۲:۱۰", image: "restaurant" },
  { id: "s3", title: "کلاس‌های شبانهٔ مهارت برای جوان‌ها رایگان شد", time: "۱۸ مهر · ۰۹:۴۵", image: "classroom" },
]
const POPULAR: { id: string; title: string; time: string; excerpt: string; image: StockImageName }[] = [
  { id: "p1", title: "مجلس طرحِ اشتغالِ جوانان را تصویب کرد", time: "۱۸ مهر · ۰۹:۱۵", excerpt: "نمایندگان طرحی را تصویب کردند که به کارفرمایانِ کوچک معافیتِ بیمه می‌دهد.", image: "studentStudy" },
  { id: "p2", title: "شفافیتِ بودجهٔ شهری؛ درخواستِ تازهٔ شوراها", time: "۱۷ مهر · ۱۱:۲۰", excerpt: "شوراها می‌خواهند هزینه‌های عمرانی ماه‌به‌ماه منتشر شود.", image: "coverWorkspace" },
  { id: "p3", title: "باشگاه‌های محله‌ای ساعتِ کارشان را بیشتر کردند", time: "۱۷ مهر · ۰۸:۰۵", excerpt: "از این هفته باشگاه‌ها تا ۲۳ باز هستند.", image: "gym" },
]

/**
 * روزنامهٔ موبایل با حالِ‌وهوای مطبوعاتی: نامِ نشریه درشت با خطِ جداکننده، ردیفِ روزهای هفته
 * با دایره‌های تاریخ (انتخابِ یک روز)، خبرِ تمام‌عرض با نوارِ قطعه‌قطعهٔ «استوری» و دکمهٔ «بعدی»،
 * و «پربازدیدها» با عکس، تیتر، زمان و یک خط خلاصه. ناوبریِ پایین با چهار مقصد.
 */
export default function MobileNewsEditorialBlock() {
  const [day, setDay] = React.useState("18")
  const [story, setStory] = React.useState(0)
  const [tab, setTab] = React.useState("feed")
  const current = STORIES[story]

  return (
    <ControlSizeProvider size="xl" asChild>
      <div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
        <header className="flex items-end justify-between gap-4 border-b border-border px-4 pt-6 pb-4">
          <h1 className="text-[1.75rem] leading-snug font-black">
            روزنامهٔ
            <br />
            آوند
          </h1>
          <Button size="icon" aria-label="اعلان‌ها" className="rounded-lg">
            <BellIcon />
          </Button>
        </header>

        <main className="flex flex-1 flex-col gap-6 pt-5 pb-6">
          <section aria-labelledby="week-title" className="flex flex-col gap-3">
            <div className="flex items-center justify-between px-4">
              <h2 id="week-title" className="font-bold">
                مرورِ هفته
              </h2>
              <span className="text-sm text-muted-foreground">مهر ۱۴۰۵</span>
            </div>
            <div className="overflow-x-auto px-4 [scrollbar-width:none]">
              <ToggleGroup type="single" separated value={day} onValueChange={(value) => value && setDay(value as string)} aria-label="روزِ هفته" className="w-max gap-2.5">
                {DAYS.map((item) => (
                  <ToggleGroupItem
                    key={item.value}
                    value={item.value}
                    aria-label={`${item.name} ${toFaDigits(item.day)} مهر`}
                    className="group h-auto flex-col gap-1.5 bg-transparent p-0 hover:bg-transparent data-[state=on]:bg-transparent"
                  >
                    <span className="flex size-14 items-center justify-center rounded-full border border-border text-xl font-black tabular-nums group-data-[state=on]:border-foreground group-data-[state=on]:bg-foreground group-data-[state=on]:text-background">
                      {toFaDigits(item.day)}
                    </span>
                    <span className="text-xs text-muted-foreground group-data-[state=on]:font-semibold group-data-[state=on]:text-foreground">{item.name}</span>
                  </ToggleGroupItem>
                ))}
              </ToggleGroup>
            </div>
          </section>

          {/* خبرِ تمام‌عرض با نوارِ قطعه‌ای؛ لمسِ «بعدی» خبرِ بعدی را می‌آورد. */}
          <section aria-label="خبرهای امروز" aria-roledescription="استوری" className="relative isolate overflow-hidden text-[var(--dig-white)]">
            {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
            <img key={current.image} src={stockImages[current.image].src} alt="" className="aspect-[4/3] w-full object-cover" />
            <span aria-hidden className="absolute inset-0 bg-linear-to-t from-[var(--dig-slate-950)] via-[color-mix(in_oklab,var(--dig-slate-950)_30%,transparent)] via-50% to-transparent" />
            <div className="absolute inset-x-0 bottom-0 flex flex-col gap-3 p-4" aria-live="polite">
              <h2 className="text-xl leading-snug font-bold text-balance">
                <a href="#">{current.title}</a>
              </h2>
              <div className="flex items-center justify-between gap-3">
                <span className="text-xs text-[var(--dig-slate-200)]">{current.time}</span>
                <ControlSizeProvider size="lg" asChild>
                  <Button onClick={() => setStory((value) => (value + 1) % STORIES.length)} className="rounded-full bg-[var(--dig-white)] px-4 text-sm text-[var(--dig-slate-900)] hover:bg-[var(--dig-slate-100)]">
                    خبرِ بعدی
                  </Button>
                </ControlSizeProvider>
              </div>
              <div aria-hidden className="flex gap-1.5">
                {STORIES.map((item, index) => (
                  <span key={item.id} className={`h-0.5 flex-1 rounded-full ${index === story ? "bg-[var(--dig-white)]" : "bg-[color-mix(in_oklab,var(--dig-white)_35%,transparent)]"}`} />
                ))}
              </div>
            </div>
          </section>

          <section aria-labelledby="popular-title" className="flex flex-col gap-1 px-4">
            <h2 id="popular-title" className="font-bold">
              پربازدیدها
            </h2>
            <ul className="flex flex-col divide-y divide-border">
              {POPULAR.map((item) => (
                <li key={item.id} className="relative flex gap-3 py-4">
                  {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                  <img src={stockImages[item.image].src} alt="" loading="lazy" className="size-24 shrink-0 object-cover" />
                  <div className="flex min-w-0 flex-col gap-1">
                    <h3 className="line-clamp-2 leading-6 font-bold">
                      <a href="#" className="after:absolute after:inset-0">
                        {item.title}
                      </a>
                    </h3>
                    <span className="text-xs text-muted-foreground">{item.time}</span>
                    <p className="line-clamp-1 text-sm text-muted-foreground">{item.excerpt}</p>
                  </div>
                </li>
              ))}
            </ul>
          </section>
        </main>

        <BottomNavigation value={tab} onValueChange={setTab} indicator="pill" fixed={false} className="sticky bottom-0">
          <BottomNavigationItem value="feed" icon={<DashboardIcon />} label="روزنامه" />
          <BottomNavigationItem value="explore" icon={<GlobeIcon />} label="کاوش" />
          <BottomNavigationItem value="saved" icon={<HeartIcon />} label="ذخیره‌ها" />
          <BottomNavigationItem value="account" icon={<UserIcon />} label="حساب" />
        </BottomNavigation>
      </div>
    </ControlSizeProvider>
  )
}