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

فیلتر در باتم‌شیت

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

فیلتر و مرتب‌سازی در شیتِ پایینیِ اپ: مرتب‌سازیِ سگمنت، دسته‌ها، بازهٔ قیمت، فقط موجود، پاک کردن و نمایشِ نتایجِ چسبان؛ شمارِ فیلترهای فعال روی دکمه.

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

این بلاک از ۱۲ آیتم دیگ استفاده می‌کند: app-barbadgebuttoncheckboxcontrol-sizedrawericonslabelpersiansliderswitchtoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/drawer.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/slider.json https://docs.digdesign.ir/r/switch.json https://docs.digdesign.ir/r/toggle-group.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/drawer.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/slider.json https://docs.digdesign.ir/r/switch.json https://docs.digdesign.ir/r/toggle-group.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/drawer.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/slider.json https://docs.digdesign.ir/r/switch.json https://docs.digdesign.ir/r/toggle-group.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/drawer.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/slider.json https://docs.digdesign.ir/r/switch.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

mobile-filters.tsx
"use client"

import * as React from "react"

import { ChevronRightIcon, FilterIcon } from "@/components/icons"
import { AppBar, AppBarLeading, AppBarTitle } from "@/components/ui/app-bar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Drawer, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle } from "@/components/ui/drawer"
import { Label } from "@/components/ui/label"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"

const CATEGORIES = ["گلدان", "ظرف", "فنجان", "جاشمعی"]
const SORTS = [
  { value: "popular", label: "پرفروش" },
  { value: "new", label: "تازه" },
  { value: "cheap", label: "ارزان" },
]

/**
 * فیلتر در باتم‌شیت (اپ): دکمهٔ «فیلتر» با شمارِ فیلترهای فعال، شیتِ پایینی با مرتب‌سازی
 * سگمنت، دسته‌ها، بازهٔ قیمت و «فقط موجود»؛ «پاک کردن» و «نمایشِ نتایج» چسبیده به پایینِ شیت.
 */
export default function MobileFiltersBlock() {
  const [open, setOpen] = React.useState(true)
  const [sort, setSort] = React.useState("popular")
  const [categories, setCategories] = React.useState<string[]>(["گلدان"])
  const [price, setPrice] = React.useState([300_000, 1_500_000])
  const [inStock, setInStock] = React.useState(true)
  const active = categories.length + (inStock ? 1 : 0) + (price[0] > 0 || price[1] < 3_000_000 ? 1 : 0)

  function reset() {
    setSort("popular")
    setCategories([])
    setPrice([0, 3_000_000])
    setInStock(false)
  }

  return (
    <ControlSizeProvider size="xl" asChild>
      <div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
        <AppBar sticky>
          <AppBarLeading>
            <Button variant="ghost" size="icon" aria-label="بازگشت">
              <ChevronRightIcon className="ltr:rotate-180" />
            </Button>
          </AppBarLeading>
          <AppBarTitle>سفال و سرامیک</AppBarTitle>
        </AppBar>
        <main className="flex flex-1 flex-col gap-4 p-4">
          <div className="flex items-center justify-between gap-3">
            <span className="text-sm text-muted-foreground">{toFaDigits(48)} کالا</span>
            <Button variant="outline" onClick={() => setOpen(true)}>
              <FilterIcon />
              فیلتر
              {active > 0 && <Badge color="primary">{toFaDigits(active)}</Badge>}
            </Button>
          </div>
          <div className="grid grid-cols-2 gap-3">
            {[stockImages.productVase, stockImages.coverMinimalRoom, stockImages.studioSunlit, stockImages.coverNotebook].map((image) => (
              // eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ
              <img key={image.src} src={image.src} alt={image.alt} className="aspect-square w-full rounded-xl object-cover" />
            ))}
          </div>
        </main>

        <Drawer open={open} onOpenChange={setOpen}>
          <ControlSizeProvider size="xl" asChild>
            <DrawerContent className="max-h-[85svh]">
              <DrawerHeader className="flex-row items-center justify-between">
                <DrawerTitle>فیلتر و مرتب‌سازی</DrawerTitle>
                <Button variant="ghost" onClick={reset}>
                  پاک کردن
                </Button>
              </DrawerHeader>
              <div className="flex flex-col gap-8 overflow-y-auto px-4 pb-4">
                <div className="flex flex-col gap-3">
                  <span id="mf-sort" className="text-sm font-medium">
                    مرتب‌سازی
                  </span>
                  <ToggleGroup type="single" variant="segmented" value={sort} onValueChange={(value) => value && setSort(value as string)} aria-labelledby="mf-sort" className="w-full">
                    {SORTS.map((item) => (
                      <ToggleGroupItem key={item.value} value={item.value} className="flex-1">
                        {item.label}
                      </ToggleGroupItem>
                    ))}
                  </ToggleGroup>
                </div>
                <fieldset className="flex flex-col gap-1">
                  <legend className="mb-2 text-sm font-medium">دسته</legend>
                  {CATEGORIES.map((category) => (
                    <div key={category} className="flex min-h-12 items-center gap-3">
                      <Checkbox
                        id={`mf-${category}`}
                        checked={categories.includes(category)}
                        onCheckedChange={(checked) => setCategories((list) => (checked ? [...list, category] : list.filter((item) => item !== category)))}
                      />
                      <Label htmlFor={`mf-${category}`} className="flex-1">
                        {category}
                      </Label>
                    </div>
                  ))}
                </fieldset>
                <div className="flex flex-col gap-4">
                  <div className="flex items-baseline justify-between gap-2">
                    <span id="mf-price" className="text-sm font-medium">
                      قیمت
                    </span>
                    <span className="text-sm text-muted-foreground tabular-nums">
                      {formatCurrency(price[0])} تا {formatCurrency(price[1])}
                    </span>
                  </div>
                  <Slider aria-labelledby="mf-price" value={price} onValueChange={setPrice} min={0} max={3_000_000} step={100_000} />
                </div>
                <div className="flex min-h-12 items-center justify-between gap-3">
                  <Label htmlFor="mf-stock">فقط کالاهای موجود</Label>
                  <Switch id="mf-stock" checked={inStock} onCheckedChange={setInStock} />
                </div>
              </div>
              <DrawerFooter className="border-t border-border pb-[max(1rem,env(safe-area-inset-bottom))]">
                <Button fullWidth onClick={() => setOpen(false)}>
                  نمایشِ {toFaDigits(48)} نتیجه
                </Button>
              </DrawerFooter>
            </DrawerContent>
          </ControlSizeProvider>
        </Drawer>
      </div>
    </ControlSizeProvider>
  )
}