کاوشِ اپ خبری
پیشنمایش تمامصفحهتیترِ بزرگ و جستوجو، «داغِ امروز» با کارتهای شمارهدارِ اسکرولخور، موضوعها با # و بازارها با نماد، درصدِ تغییرِ رنگی و عکس.
۱. نصب کامپوننتها
این بلاک از ۶ آیتم دیگ استفاده میکند: 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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}