اتوکامپلیت کاربران
کامپوننت اختصاصی جستجوی کاربران با Infinite Scroll
معرفی
کامپوننت UserAutocomplete یک input اختصاصی برای جستجوی کاربران است که شامل:
- نمایش آواتار، نام، یوزرنیم و تعداد فالوورها
- فرمت هوشمند فالوورها (K, M, B)
- Infinite Scroll (بارگذاری خودکار با اسکرول)
- جستجوی Debounced
- Keyboard Navigation
- پشتیبانی کامل از API
- RTL Support
استفاده پایه
با API و Infinite Scroll
مثال کامل با API و بارگذاری صفحات بیشتر:
مثال ساده (بدون API)
برای دادههای استاتیک:
نسخه LTR
Props
UserAutocomplete
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 برای شناسایی زمانی که کاربر به انتهای لیست رسیده استفاده میکند:
- وقتی کاربر اسکرول میکند و به انتهای لیست میرسد
- تابع
onLoadMoreفراخوانی میشود isLoadingMoreفعال میشود و Spinner نمایش داده میشود- صفحه بعدی از API بارگذاری میشود
- کاربران جدید به لیست فعلی اضافه میشوند
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);
};فرمت کردن تعداد فالوورها
کامپوننت به طور خودکار فالوورها را فرمت میکند:
| تعداد | نمایش |
|---|---|
| 850 | 850 |
| 1,200 | 1.2K |
| 45,000 | 45K |
| 1,500,000 | 1.5M |
| 2,300,000,000 | 2.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 در انتهای لیست تنظیم کنید