پرتوپرتو

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

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

معرفی

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

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

استفاده پایه

با 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]: any;  // فیلدهای اضافی
}

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

جستجوی کاربران با 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بستن منوی نتایج

ویژگی‌های کلیدی

  • Infinite Scroll: بارگذاری خودکار با اسکرول
  • Debounced Search: جلوگیری از درخواست‌های زیاد
  • Smart Formatting: فرمت هوشمند فالوورها (K, M, B)
  • Avatar Support: نمایش تصویر پروفایل
  • Keyboard Navigation: پیمایش با کیبورد
  • API Ready: آماده برای استفاده با API
  • RTL Support: پشتیبانی کامل از راست به چپ
  • Persian Numbers: تبدیل خودکار اعداد
  • Type Safe: TypeScript با تعریف دقیق
  • Customizable: قابل سفارشی‌سازی

نکات مهم

  • برای infinite scroll، enableInfiniteScroll را true کنید
  • تابع onSearch هم query و هم page را دریافت می‌کند
  • hasMore را برای کنترل نمایش بارگذاری بیشتر تنظیم کنید
  • برای بهینه‌سازی، از debounceDelay مناسب استفاده کنید
  • isLoadingMore را برای نمایش Spinner در انتهای لیست تنظیم کنید