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

جست‌وجوی موبایل

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

نوار جست‌وجوی تمام‌عرض، جست‌وجوهای اخیر و پرطرفدار، نتیجه‌ها و حالت بی‌نتیجه.

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

این بلاک از ۶ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeempty-stateiconspersiansearch-field

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.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-search.tsx
"use client"

import * as React from "react"

import { ChevronRightIcon, ClockIcon, ImageIcon, XIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { SearchField } from "@/components/ui/search-field"
import { formatCurrency, toSearchKey } from "@/lib/persian"

const PRODUCTS = [
  { id: 1, title: "هدفون بی‌سیم دیگ پرو", category: "هدفون", price: 3_490_000 },
  { id: 2, title: "هدفون گیمینگ دیگ", category: "هدفون", price: 4_750_000 },
  { id: 3, title: "اسپیکر آوا بوم", category: "اسپیکر", price: 2_300_000 },
  { id: 4, title: "ساعت هوشمند دیگ فیت", category: "ساعت هوشمند", price: 5_750_000 },
  { id: 5, title: "ایربادز سونیک لایت", category: "هدفون", price: 1_250_000 },
]
const POPULAR = ["هدفون", "ساعت هوشمند", "پاوربانک", "کیف لپ‌تاپ"]

export default function MobileSearchBlock() {
  const [query, setQuery] = React.useState("")
  const [recent, setRecent] = React.useState(["هدفون بی‌سیم", "اسپیکر", "کابل تایپ‌سی"])
  const key = toSearchKey(query)
  const results = key ? PRODUCTS.filter((item) => toSearchKey(item.title + item.category).includes(key)) : []

  function search(term: string) {
    setQuery(term)
    setRecent((list) => [term, ...list.filter((item) => item !== term)].slice(0, 5))
  }

  return (
    <ControlSizeProvider size="xl" asChild>
    <div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
      <h1 className="sr-only">جست‌وجو</h1>
      {/* نوارِ جست‌وجوی تمام‌عرض جای عنوان را می‌گیرد؛ فوکوس از اول روی فیلد است. */}
      <header className="sticky top-0 z-10 flex items-center gap-2 border-b border-border bg-background p-3">
        <Button variant="ghost" size="icon" aria-label="بازگشت">
          <ChevronRightIcon className="ltr:rotate-180" />
        </Button>
        <SearchField
          aria-label="جست‌وجو در فروشگاه"
          placeholder="جست‌وجوی کالا…"
          value={query}
          onValueChange={setQuery}
          onSearch={(value) => value && search(value)}
          className="flex-1"
        />
      </header>

      <main className="flex flex-1 flex-col gap-6 p-4" aria-live="polite">
        {!query && (
          <>
            {recent.length > 0 && (
              <section aria-labelledby="recent" className="flex flex-col gap-2">
                <div className="flex items-center justify-between">
                  <h2 id="recent" className="font-semibold">
                    جست‌وجوهای اخیر
                  </h2>
                  <Button variant="ghost" onClick={() => setRecent([])}>
                    پاک کردن
                  </Button>
                </div>
                <ul className="flex flex-col">
                  {recent.map((term) => (
                    <li key={term} className="flex min-h-12 items-center gap-3">
                      <ClockIcon className="size-4 shrink-0 text-muted-foreground" />
                      <Button variant="ghost" className="flex-1 justify-start px-0 font-normal" onClick={() => search(term)}>
                        {term}
                      </Button>
                      <Button
                        variant="ghost"
                        size="icon"
                        aria-label={`حذف «${term}» از جست‌وجوهای اخیر`}
                        onClick={() => setRecent((list) => list.filter((item) => item !== term))}
                      >
                        <XIcon />
                      </Button>
                    </li>
                  ))}
                </ul>
              </section>
            )}
            <section aria-labelledby="popular" className="flex flex-col gap-3">
              <h2 id="popular" className="font-semibold">
                جست‌وجوهای پرطرفدار
              </h2>
              <div className="flex flex-wrap gap-2">
                {POPULAR.map((term) => (
                  <Button key={term} variant="secondary" className="rounded-full" onClick={() => search(term)}>
                    {term}
                  </Button>
                ))}
              </div>
            </section>
          </>
        )}

        {query && results.length > 0 && (
          <ul className="flex flex-col divide-y divide-border">
            {results.map((item) => (
              <li key={item.id}>
                <a href="#" className="flex items-center gap-3 py-3">
                  <span className="flex size-14 shrink-0 items-center justify-center rounded-xl bg-muted text-placeholder">
                    <ImageIcon className="size-6" />
                  </span>
                  <span className="flex min-w-0 flex-1 flex-col gap-1">
                    <span className="truncate font-medium">{item.title}</span>
                    <span className="text-xs text-muted-foreground">{item.category}</span>
                  </span>
                  <span className="shrink-0 text-sm font-semibold">{formatCurrency(item.price)}</span>
                </a>
              </li>
            ))}
          </ul>
        )}

        {query && results.length === 0 && (
          <EmptyState>
            <EmptyStateTitle>کالایی با «{query}» پیدا نشد</EmptyStateTitle>
            <EmptyStateDescription>املای کلمه را بررسی کنید یا عبارتِ کوتاه‌تری بنویسید.</EmptyStateDescription>
          </EmptyState>
        )}
      </main>
    </div>
    </ControlSizeProvider>
  )
}