پرتوپرتو

اتوکامپلیت کاربران

کامپوننت اختصاصی جستجوی کاربران با Infinite Scroll

معرفی

کامپوننت UserAutocomplete یک input اختصاصی برای جستجوی کاربران است که شامل:

  • نمایش آواتار، نام، یوزرنیم و تعداد فالوورها
  • فرمت هوشمند فالوورها (K, M, B)
  • Infinite Scroll (بارگذاری خودکار با اسکرول)
  • جستجوی Debounced
  • Keyboard Navigation
  • پشتیبانی کامل از API
  • RTL Support

چه زمانی استفاده کنیم:

  • وقتی فیلد جستجو مخصوص انتخاب کاربر است و نیاز به نمایش آواتار، نام و فالوور دارید
  • وقتی لیست کاربران بزرگ است و نیاز به Infinite Scroll دارید
  • وقتی جستجوی کاربران از API سرور انجام می‌شود

چه زمانی استفاده نکنیم:

  • برای جستجوی موجودیت‌های غیر کاربر — از Autocomplete استفاده کنید
  • برای انتخاب کاربر از لیست کوچک و ثابت — از Select استفاده کنید

استفاده پایه

با API و Infinite Scroll

مثال کامل با API و بارگذاری صفحات بیشتر:

مثال ساده (بدون API)

برای داده‌های استاتیک:

نسخه LTR

Props

UserAutocomplete

Prop

Type

UserItem

interface UserItem {
  id: string // شناسه یکتا
  name: string // نام کاربر
  username: string // نام کاربری
  avatar?: string // آدرس تصویر پروفایل
  followers: number // تعداد فالوورها
  [key: string]: unknown // فیلدهای اضافی
}

موارد استفاده

جستجوی کاربران با API

import { UserAutocomplete, UserItem } from '@parto-system-design/ui'
import { useState } from 'react'

export function UserSearch() {
  const [value, setValue] = useState('')
  const [users, setUsers] = useState<UserItem[]>([])
  const [isLoading, setIsLoading] = useState(false)
  const [isLoadingMore, setIsLoadingMore] = useState(false)
  const [hasMore, setHasMore] = useState(true)
  const [page, setPage] = useState(1)

  const handleSearch = async (query: string, pageNum: number) => {
    if (pageNum === 1) {
      setIsLoading(true)
      setUsers([])
      setPage(1)
    } else {
      setIsLoadingMore(true)
    }

    try {
      const response = await fetch(`/api/users/search?q=${query}&page=${pageNum}&pageSize=10`)
      const data = await response.json()

      if (pageNum === 1) {
        setUsers(data.users)
      } else {
        setUsers((prev) => [...prev, ...data.users])
      }

      setHasMore(data.hasMore)
      setPage(pageNum)
    } catch (error) {
      console.error(error)
    } finally {
      setIsLoading(false)
      setIsLoadingMore(false)
    }
  }

  const handleLoadMore = async () => {
    await handleSearch(value, page + 1)
  }

  return (
    <UserAutocomplete
      value={value}
      onValueChange={setValue}
      users={users}
      isLoading={isLoading}
      isLoadingMore={isLoadingMore}
      hasMore={hasMore}
      onSearch={handleSearch}
      onLoadMore={handleLoadMore}
      placeholder="جستجوی کاربر..."
      dir="rtl"
      enableInfiniteScroll={true}
      onSelect={(user) => {
        console.log('Selected user:', user)
      }}
    />
  )
}

استفاده ساده (بدون API)

import { UserAutocomplete } from '@parto-system-design/ui'
import { useState } from 'react'

const mockUsers = [
  {
    id: '1',
    name: 'محسن یگانه',
    username: 'mohsene',
    followers: 7864554,
    avatar: '/avatars/mohsen.jpg',
  },
  {
    id: '2',
    name: 'علی کریمی',
    username: 'alikarimi8',
    followers: 4605123,
    avatar: '/avatars/ali.jpg',
  },
]

export function SimpleUserSearch() {
  const [value, setValue] = useState('')

  return (
    <UserAutocomplete
      value={value}
      onValueChange={setValue}
      users={mockUsers}
      placeholder="جستجوی کاربر..."
      minChars={0}
      dir="rtl"
      enableInfiniteScroll={false}
    />
  )
}

فیلتر کردن کاربران Local

import { UserAutocomplete } from '@parto-system-design/ui'
import { useState } from 'react'

export function FilterUsers() {
  const [value, setValue] = useState('')
  const [filteredUsers, setFilteredUsers] = useState([])

  const allUsers = [
    // ... لیست کاربران
  ]

  const handleSearch = (query: string) => {
    const filtered = allUsers.filter((user) => user.name.includes(query) || user.username.includes(query))
    setFilteredUsers(filtered)
  }

  return (
    <UserAutocomplete
      value={value}
      onValueChange={setValue}
      users={filteredUsers}
      onSearch={handleSearch}
      placeholder="جستجوی کاربر..."
      dir="rtl"
      enableInfiniteScroll={false}
    />
  )
}

انتخاب چندین کاربر

import { UserAutocomplete, UserItem } from '@parto-system-design/ui'
import { useState } from 'react'

export function MultiUserSelect() {
  const [value, setValue] = useState('')
  const [selectedUsers, setSelectedUsers] = useState<UserItem[]>([])

  return (
    <div className="space-y-4" dir="rtl">
      <UserAutocomplete
        value={value}
        onValueChange={setValue}
        // ... other props
        clearOnSelect={true}
        onSelect={(user) => {
          setSelectedUsers((prev) => [...prev, user])
        }}
      />

      {/* نمایش کاربران انتخاب شده */}
      <div className="flex flex-wrap gap-2">
        {selectedUsers.map((user) => (
          <div key={user.id} className="flex items-center gap-2 bg-accent px-3 py-1 rounded-full">
            <img src={user.avatar} className="w-6 h-6 rounded-full" />
            <span className="text-sm">{user.name}</span>
            <button onClick={() => setSelectedUsers((prev) => prev.filter((u) => u.id !== user.id))}>×</button>
          </div>
        ))}
      </div>
    </div>
  )
}

Infinite Scroll چگونه کار می‌کند؟

کامپوننت از Intersection Observer API برای شناسایی زمانی که کاربر به انتهای لیست رسیده استفاده می‌کند:

  1. وقتی کاربر اسکرول می‌کند و به انتهای لیست می‌رسد
  2. تابع onLoadMore فراخوانی می‌شود
  3. isLoadingMore فعال می‌شود و Spinner نمایش داده می‌شود
  4. صفحه بعدی از API بارگذاری می‌شود
  5. کاربران جدید به لیست فعلی اضافه می‌شوند
const handleLoadMore = async () => {
  const nextPage = page + 1
  const response = await fetch(`/api/users?page=${nextPage}`)
  const data = await response.json()

  // اضافه کردن کاربران جدید
  setUsers((prev) => [...prev, ...data.users])
  setPage(nextPage)
  setHasMore(data.hasMore)
}

فرمت کردن تعداد فالوورها

کامپوننت به طور خودکار فالوورها را فرمت می‌کند:

تعدادنمایش
850850
1,2001.2K
45,00045K
1,500,0001.5M
2,300,000,0002.3B

Keyboard Shortcuts

کلیدعملکرد
↓ Arrow Downحرکت به کاربر بعدی
↑ Arrow Upحرکت به کاربر قبلی
Enterانتخاب کاربر فعال
Escapeبستن منوی نتایج

حالت‌ها و انواع

با API و Infinite Scroll

حالت پیش‌فرض که enableInfiniteScroll={true} دارد و هنگام رسیدن به انتهای لیست، onLoadMore فراخوانی می‌شود.

بدون API (داده استاتیک)

با enableInfiniteScroll={false} و ارسال مستقیم آرایه کاربران.

پاک‌سازی پس از انتخاب

با clearOnSelect={true} برای سناریوهایی مانند انتخاب چندین کاربر پشت سر هم.

راهنمای استفاده

بکنید

  • برای Infinite Scroll، حتماً hasMore و isLoadingMore را به‌درستی مدیریت کنید - از debounceDelay مناسب استفاده کنید تا درخواست‌های اضافی به سرور ارسال نشود - onSearch را طوری پیاده‌سازی کنید که هم query و هم page را مدیریت کند

نکنید

  • از این کامپوننت برای جستجوی موجودیت‌های غیر کاربر استفاده نکنید — Autocomplete مناسب‌تر است - بدون مدیریت hasMore، بارگذاری بی‌نهایت ایجاد نکنید - enableInfiniteScroll را بدون تعریف onLoadMore فعال نکنید

دسترسی‌پذیری

  • پیمایش بین کاربران با کلیدهای و
  • انتخاب کاربر فعال با Enter
  • بستن منوی نتایج با Escape
  • تبدیل خودکار اعداد فالوور به فرمت فارسی
  • پشتیبانی کامل از RTL و LTR

کامپوننت‌های مرتبط

  • اگر نیاز به جستجوی موجودیت‌های غیر کاربر دارید → Autocomplete
  • اگر نیاز به انتخاب چندین کاربر از لیست ثابت دارید → MultiSelect
  • اگر فقط نیاز به انتخاب یک کاربر از لیست کوچک دارید → Select