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

تیترِ بزرگ و جست‌وجو، «داغِ امروز» با کارت‌های شماره‌دارِ اسکرول‌خور، موضوع‌ها با # و بازارها با نماد، درصدِ تغییرِ رنگی و عکس.

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

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

پکیج‌منیجر پیش‌فرض 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/search-field.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/search-field.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/search-field.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/search-field.json

۲. کد صفحه

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

mobile-news-explore.tsx
"use client"

import * as React from "react"

import { DashboardIcon, HeartIcon, SearchIcon, TrendingDownIcon, TrendingUpIcon, 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 { SearchField } from "@/components/ui/search-field"
import { toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

const TRENDING = [
  { id: "t1", title: "قیمتِ مسکن در پاییز؛ خریدارها منتظر می‌مانند", hours: 2, minutes: 4 },
  { id: "t2", title: "سه اپِ تازه که پس‌انداز را بازی کرده‌اند", hours: 3, minutes: 3 },
  { id: "t3", title: "مالیاتِ کسب‌وکارهای خانگی، به زبانِ ساده", hours: 5, minutes: 6 },
]
const TOPICS = ["طلا", "بورس", "مسکن", "رمزارز", "استارتاپ", "خودرو", "بیمه"]
const MARKETS: { id: string; symbol: string; change: number; title: string; image: StockImageName }[] = [
  { id: "m1", symbol: "طلای ۱۸", change: 1.2, title: "طلا سه روزِ پشتِ‌سرِهم بالا رفت؛ تحلیل‌گرها محتاط‌اند", image: "coverFinanceDesk" },
  { id: "m2", symbol: "شاخص کل", change: -0.8, title: "بورس بعد از رشدِ هفتهٔ پیش کمی عقب نشست", image: "projectDashboard" },
  { id: "m3", symbol: "دلار", change: 0.3, title: "بازارِ ارز آرام؛ معامله‌ها کم‌حجم ماند", image: "coverCalculator" },
]

/**
 * کاوشِ اپِ خبری: تیترِ بزرگ و جست‌وجوی تمام‌عرض، «داغِ امروز» با کارت‌های شماره‌دارِ اسکرول‌خور
 * (عددِ درشت، تیتر، زمان)، موضوع‌ها با برچسبِ # و «بازارها» با نمادِ هر بازار، درصدِ تغییر به
 * کلمه و رنگ (بالا/پایین)، تیتر و عکسِ کوچک.
 */
export default function MobileNewsExploreBlock() {
  const [query, setQuery] = React.useState("")
  const [tab, setTab] = React.useState("explore")

  return (
    <ControlSizeProvider size="xl" asChild>
      <div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
        <main className="flex flex-1 flex-col gap-8 pt-6 pb-6">
          <header className="flex flex-col gap-4 px-4">
            <h1 className="text-3xl leading-snug font-black">کاوش</h1>
            <SearchField aria-label="جست‌وجوی خبر" placeholder="خبر، نویسنده یا موضوع" value={query} onValueChange={setQuery} />
          </header>

          <section aria-labelledby="hot-title" className="flex flex-col gap-3">
            <h2 id="hot-title" className="px-4 text-sm font-bold text-muted-foreground">
              # داغِ امروز
            </h2>
            <ol className="flex snap-x snap-mandatory gap-3 overflow-x-auto scroll-px-4 px-4 [scrollbar-width:none]">
              {TRENDING.map((item, index) => (
                <li key={item.id} className="relative flex w-64 shrink-0 snap-start flex-col gap-3 rounded-xl border border-border bg-card p-4 shadow-card">
                  <span aria-hidden className="text-3xl leading-none font-black text-[var(--dig-orange-600)] dark:text-[var(--dig-orange-400)]">
                    #{toFaDigits(index + 1)}
                  </span>
                  <h3 className="line-clamp-3 min-h-18 leading-6 font-bold">
                    <a href="#" className="after:absolute after:inset-0">
                      {item.title}
                    </a>
                  </h3>
                  <span className="text-xs text-muted-foreground">
                    {toFaDigits(item.hours)} ساعت پیش · {toFaDigits(item.minutes)} دقیقه مطالعه
                  </span>
                </li>
              ))}
            </ol>
          </section>

          <section aria-labelledby="topics-title" className="flex flex-col gap-3 px-4">
            <h2 id="topics-title" className="text-sm font-bold text-muted-foreground">
              # موضوع‌ها
            </h2>
            <ControlSizeProvider size="lg" className="flex flex-wrap gap-2">
              {TOPICS.map((topic) => (
                <Button key={topic} variant="secondary" className="rounded-full px-4">
                  #{topic}
                </Button>
              ))}
            </ControlSizeProvider>
          </section>

          <section aria-labelledby="markets-title" className="flex flex-col gap-1 px-4">
            <h2 id="markets-title" className="text-sm font-bold text-muted-foreground">
              # بازارها
            </h2>
            <ul className="flex flex-col divide-y divide-border">
              {MARKETS.map((item) => {
                const up = item.change >= 0
                return (
                  <li key={item.id} className="relative flex items-center gap-3 py-4">
                    <div className="flex min-w-0 flex-1 flex-col gap-2">
                      <span className="flex w-fit items-center gap-2 rounded-full border border-border px-2.5 py-1 text-xs">
                        <span className="font-semibold">{item.symbol}</span>
                        <span className={`flex items-center gap-0.5 font-bold tabular-nums [&>svg]:size-3.5 ${up ? "text-success" : "text-destructive"}`}>
                          {up ? <TrendingUpIcon aria-hidden /> : <TrendingDownIcon aria-hidden />}
                          <span className="sr-only">{up ? "رشد" : "افت"}</span>
                          {toFaDigits(String(Math.abs(item.change)).replace(".", "٫"))}٪
                        </span>
                      </span>
                      <h3 className="line-clamp-2 leading-6 font-semibold">
                        <a href="#" className="after:absolute after:inset-0">
                          {item.title}
                        </a>
                      </h3>
                    </div>
                    {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                    <img src={stockImages[item.image].src} alt="" loading="lazy" className="size-16 shrink-0 rounded-lg object-cover" />
                  </li>
                )
              })}
            </ul>
          </section>
        </main>

        <BottomNavigation value={tab} onValueChange={setTab} indicator="pill" fixed={false} className="sticky bottom-0">
          <BottomNavigationItem value="home" icon={<DashboardIcon />} label="خانه" />
          <BottomNavigationItem value="explore" icon={<SearchIcon />} label="کاوش" />
          <BottomNavigationItem value="saved" icon={<HeartIcon />} label="ذخیره‌ها" />
          <BottomNavigationItem value="profile" icon={<UserIcon />} label="پروفایل" />
        </BottomNavigation>
      </div>
    </ControlSizeProvider>
  )
}