DataTable
Ma'lumotlar jadvali: saralash, o'zbekcha qidiruv, filtrlar, tanlash, sahifalash, server rejimi va virtual scroll.
| Holat | |||||
|---|---|---|---|---|---|
| #1042 | Jasur Karimov | Toshkent | 21.10.2026 | To'langan | 85 000 so'm |
| #1043 | Nodira Saidova | Buxoro | 20.10.2026 | Kutilmoqda | 462 311 so'm |
| #1044 | Malika Rahimova | Xorazm | 19.10.2026 | Yo'lda | 839 622 so'm |
| #1045 | Aziz Normatov | Samarqand | 18.10.2026 | Bekor qilingan | 1 216 933 so'm |
| #1046 | Bekzod G'aniyev | Farg'ona | 17.10.2026 | Kutilmoqda | 1 594 244 so'm |
'use client';
import { formatDate, formatMoney } from '@akhatmirzo/core';
import { Badge, Button, DataTable, toast, type DataTableColumn } from '@akhatmirzo/ui';
import { Plus } from 'lucide-react';
type Status = 'paid' | 'pending' | 'shipped' | 'cancelled';
interface Order {
id: number;
customer: string;
region: string;
date: Date;
status: Status;
total: number;
}
const STATUS = {
paid: { label: "To'langan", color: 'success' },
pending: { label: 'Kutilmoqda', color: 'warning' },
shipped: { label: "Yo'lda", color: 'info' },
cancelled: { label: 'Bekor qilingan', color: 'danger' },
} as const;
const NAMES = [
'Jasur Karimov',
'Malika Rahimova',
"Bekzod G'aniyev",
'Nodira Saidova',
'Aziz Normatov',
];
const REGIONS = ['Toshkent', 'Samarqand', 'Buxoro', "Farg'ona", 'Xorazm'];
const STATUSES = Object.keys(STATUS) as Status[];
const orders: Order[] = Array.from({ length: 48 }, (_, i) => ({
id: 1042 + i,
customer: NAMES[(i * 3) % NAMES.length]!,
region: REGIONS[(i * 7) % REGIONS.length]!,
date: new Date(2026, 9, 21 - (i % 30)),
status: STATUSES[(i * 5 + (i >> 2)) % 4]!,
total: 85_000 + ((i * 377_311) % 4_800_000),
}));
const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№', width: 88, sortable: true, cell: (o) => `#${o.id}` },
{ key: 'customer', header: 'Mijoz', sortable: true },
{ key: 'region', header: 'Viloyat', sortable: true, filter: 'select' },
{ key: 'date', header: 'Sana', sortable: true, cell: (o) => formatDate(o.date) },
{
key: 'status',
header: 'Holat',
filter: 'multiselect',
filterOptions: STATUSES.map((s) => ({ value: s, label: STATUS[s].label })),
searchValue: (o) => STATUS[o.status].label,
cell: (o) => (
<Badge dot color={STATUS[o.status].color}>
{STATUS[o.status].label}
</Badge>
),
},
{
key: 'total',
header: 'Summa',
numeric: true,
sortable: true,
cell: (o) => formatMoney(o.total),
},
];
export default function DataTableBasic() {
return (
<DataTable
label="Buyurtmalar"
data={orders}
columns={columns}
searchable
pagination={{ pageSize: 5, pageSizeOptions: [5, 10, 25] }}
className="w-full"
toolbar={
<Button variant="primary" leftIcon={<Plus />} onClick={() => toast('Yangi buyurtma')}>
Qo'shish
</Button>
}
/>
);
}
Import
import { DataTable, type DataTableColumn } from '@akhatmirzo/ui';DataTable TanStack Table ustida qurilgan, lekin sizdan uning
API'sini bilish talab qilinmaydi: ma'lumot (data) va ustunlar ro'yxati (columns) yetarli —
saralash, qidiruv, filtr, sahifalash va holatlar (yuklanish, bo'sh, xato) tayyor.
Qachon ishlatish
- Bir xil tuzilishdagi ko'p yozuv uchun — buyurtmalar, mijozlar, to'lovlar, xodimlar. Foydalanuvchi solishtiradi, saralaydi, qidiradi.
- Kichik ro'yxat uchun (5–10 qator, saralash kerak emas) oddiy
<table>yoki Cardlar yetarli. - Ma'lumot ko'p bo'lsa (minglab qator) — ikki yo'l: serverda sahifalash (
manual) yoki hammasi brauzerda bo'lsa —virtual. - Qatordan boshqa sahifaga o'tish kerak bo'lsa —
onRowClick; bir nechta amal bo'lsa —rowActions(odatda DropdownMenu). - Sonli ustunlarga
numericbering — o'ngga tekislanadi va raqamlar bir xil kenglikda (tabular-nums) chiziladi, summalarni ko'z bilan solishtirish oson.
Ustunlar
Har bir ustun — DataTableColumn<T> obyekti. Eng kami — key va header; qiymat row[key] dan
olinadi va matn sifatida chiziladi. Qolgan hamma narsa ixtiyoriy:
import { formatDate, formatMoney } from '@akhatmirzo/core';
import { Badge, type DataTableColumn } from '@akhatmirzo/ui';
export interface Order {
id: number;
customer: { name: string; phone: string };
date: Date;
status: 'paid' | 'pending';
total: number;
}
export const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№', width: 88, sortable: true, hideable: false },
{
key: 'customer',
header: 'Mijoz',
accessor: (o) => o.customer.name, // saralash, filtr va qidiruv shu qiymat bo'yicha
sortable: true,
},
{
key: 'phone',
header: 'Telefon',
accessor: (o) => o.customer.phone,
hidden: true, // "Ustunlar" menyusidan yoqiladi
},
{
key: 'date',
header: 'Sana',
sortable: true,
filter: 'date',
searchable: false,
cell: (o) => formatDate(o.date, 'dd.MM.yyyy, HH:mm'),
},
{
key: 'status',
header: 'Holat',
filter: 'multiselect',
filterOptions: [
{ value: 'paid', label: "To'langan" },
{ value: 'pending', label: 'Kutilmoqda' },
],
searchValue: (o) => (o.status === 'paid' ? "To'langan" : 'Kutilmoqda'),
cell: (o) =>
o.status === 'paid' ? <Badge color="success">To'langan</Badge> : <Badge>Kutilmoqda</Badge>,
},
{
key: 'total',
header: 'Summa',
numeric: true,
sortable: true,
filter: 'range',
cell: (o) => formatMoney(o.total),
},
];| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
keymajburiy | string | — | Ustun id'si; accessor bo'lmasa — row[key] qiymati |
headermajburiy | ReactNode | — | Sarlavha |
accessor | (row: T) => unknown | — | Saralash, filtr va qidiruv uchun qiymat (standart — row[key]) |
cell | (row: T) => ReactNode | — | Katak ko'rinishi (standart — qiymat matni; sana — dd.MM.yyyy) |
width | number | string | — | Kenglik: px yoki CSS qiymat |
align | 'left' | 'center' | 'right' | — | Tekislash (standart — left, numeric bo'lsa right) |
numeric | boolean | — | O'ngga tekislanadi, tabular-nums |
sortable | boolean | — | Sarlavha bosilsa saralanadi |
filter | 'select' | 'multiselect' | 'range' | 'date' | — | Filtr turi ("Filtrlar" oynasida) |
filterOptions | DataTableFilterOption[] | — | select / multiselect variantlari { value, label } (standart — ma'lumotdagi noyob qiymatlar) |
filterLabel | string | — | Filtr va badge'dagi nom (standart — header matni yoki key) |
searchable | boolean | true | Umumiy qidiruvda qatnashadimi |
searchValue | (row: T) => string | — | Qidiruv uchun matn (masalan, holat kodi o'rniga uning nomi) |
hideable | boolean | true | "Ustunlar" menyusida yashirish mumkinmi |
hidden | boolean | — | Boshlang'ich holatda yashirin |
className | string | — | Katak (td) klassi |
Qiymat va ko'rinishni ajrating
cell faqat ko'rinish uchun. Saralash, filtr va qidiruv qiymat bilan ishlaydi (accessor
yoki row[key]). Shuning uchun summani number, sanani Date holida saqlang va cell da
formatlang — saralash to'g'ri bo'ladi, qidiruvda ham "1042" va "1 042" ikkalasi topiladi.
Saralash
sortable ustun sarlavhasini bosing: o'sish → kamayish → o'chiq. Shift bilan bosilsa, ustun
avvalgisiga qo'shiladi (bir nechta ustun bo'yicha saralash), sarlavhada tartib raqami
ko'rinadi. Saralash qiymat turini taniydi: son, sana, mantiqiy qiymat va matn (o'zbek alifbosi
bo'yicha, "10" > "9"); bo'sh qiymatlar oxirida turadi.
Boshlang'ich saralash — initialState:
<DataTable
data={orders}
columns={columns}
initialState={{ sorting: [{ id: 'date', desc: true }] }}
/>Qidiruv
searchable jadval ustiga qidiruv maydonini qo'shadi. Qidiruv 300ms kechikish (debounce) bilan
ishlaydi va o'zbek yozuviga moslashgan: kirill va lotin farqsiz (жасур → "Jasur"), oʻ, o'
va o‘ bir xil. Qidiruv barcha ustunlarda (searchable: false dan tashqari) ishlaydi; ko'rinishi
boshqa bo'lgan ustunlar uchun searchValue bering.
Filtrlar
Ustunga filter bersangiz, asboblar panelida "Filtrlar" tugmasi paydo bo'ladi. Faol filtrlar
jadval ustida badge sifatida ko'rinadi va ✕ bilan o'chiriladi. Hech narsa topilmasa —
"Filtrlarni tozalash" tugmasi.
| Tur | Boshqaruv | Qiymat (DataTableFilterValue) |
|---|---|---|
select | Radio: bitta qiymat | string |
multiselect | Checkbox'lar: bir nechta qiymat | string[] |
range | Ikki son: "dan" — "gacha" | { min?: number; max?: number } |
date | Ikki sana: boshlanish — tugash | { from?: Date | null; to?: Date | null } |
select va multiselect variantlari filterOptions dan, bo'lmasa ma'lumotdagi noyob
qiymatlardan olinadi — har birining yonida nechta qator borligi ko'rsatiladi. date oralig'ida
chegaralar kiradi (tugash kuni ham). Quyidagi demoda boshlang'ich filtr initialState orqali
berilgan.
| № | Mijoz | To'lov turi | Viloyat | ||
|---|---|---|---|---|---|
| #501 | Dilnoza Yusupova | Click | Toshkent | 25.10.2026 | 50 000 so'm |
| #505 | Dilnoza Yusupova | Click | Samarqand | 21.10.2026 | 903 828 so'm |
| #509 | Dilnoza Yusupova | Click | Andijon | 17.10.2026 | 1 757 656 so'm |
| #513 | Dilnoza Yusupova | Click | Namangan | 13.10.2026 | 2 611 484 so'm |
| #517 | Dilnoza Yusupova | Click | Toshkent | 09.10.2026 | 465 312 so'm |
'use client';
import { formatDate, formatMoney } from '@akhatmirzo/core';
import { DataTable, type DataTableColumn } from '@akhatmirzo/ui';
interface Payment {
id: number;
client: string;
method: string;
region: string;
date: Date;
amount: number;
}
const METHODS = ['Click', 'Payme', 'Uzcard', 'Naqd'];
const REGIONS = ['Toshkent', 'Samarqand', 'Andijon', 'Namangan'];
const CLIENTS = ['Dilnoza Yusupova', 'Otabek Qodirov', 'Shahnoza Ergasheva', 'Rustam Xolmatov'];
const payments: Payment[] = Array.from({ length: 40 }, (_, i) => ({
id: 501 + i,
client: CLIENTS[(i * 3) % CLIENTS.length]!,
method: METHODS[(i * 5) % METHODS.length]!,
region: REGIONS[(i + (i >> 2)) % REGIONS.length]!,
date: new Date(2026, 9, 25 - i),
amount: 50_000 + ((i * 213_457) % 3_000_000),
}));
const columns: DataTableColumn<Payment>[] = [
{ key: 'id', header: '№', width: 72, cell: (p) => `#${p.id}` },
{ key: 'client', header: 'Mijoz' },
{ key: 'method', header: "To'lov turi", filter: 'select' },
{ key: 'region', header: 'Viloyat', filter: 'multiselect' },
{ key: 'date', header: 'Sana', filter: 'date', sortable: true, cell: (p) => formatDate(p.date) },
{
key: 'amount',
header: 'Summa',
numeric: true,
sortable: true,
filter: 'range',
cell: (p) => formatMoney(p.amount),
},
];
export default function DataTableFilters() {
return (
<DataTable
label="To'lovlar"
data={payments}
columns={columns}
searchable
pagination={{ pageSize: 5 }}
initialState={{ filters: { method: 'Click' } }}
className="w-full"
/>
);
}
Tanlash va ommaviy amallar
selectable — chapda Checkbox. Sarlavhadagi Checkbox joriy sahifani tanlaydi (qisman tanlanganda
— indeterminate). Qator tanlansa, asboblar paneli ommaviy amallar paneliga aylanadi: "2 ta
tanlandi", bulkActions dagi tugmalar va "Bekor qilish". Shift bilan Checkbox bosilsa, oraliq
tanlanadi.
| Artikul | Amallar | |||
|---|---|---|---|---|
| Paxta ko'ylak | KY-001 | 42 | ||
| Atlas ro'mol | RM-014 | 8 | ||
| Choynak (1.5 l) | CH-203 | 0 | ||
| Do'ppi | DP-077 | 120 | ||
| Sopol lagan | LG-310 | 15 |
'use client';
import {
DataTable,
DropdownMenu,
IconButton,
toast,
type DataTableColumn,
Button,
} from '@akhatmirzo/ui';
import { Download, Ellipsis, Eye, Trash2 } from 'lucide-react';
import { useState } from 'react';
interface Product {
id: string;
name: string;
sku: string;
stock: number;
}
const initial: Product[] = [
{ id: 'p1', name: "Paxta ko'ylak", sku: 'KY-001', stock: 42 },
{ id: 'p2', name: "Atlas ro'mol", sku: 'RM-014', stock: 8 },
{ id: 'p3', name: 'Choynak (1.5 l)', sku: 'CH-203', stock: 0 },
{ id: 'p4', name: "Do'ppi", sku: 'DP-077', stock: 120 },
{ id: 'p5', name: 'Sopol lagan', sku: 'LG-310', stock: 15 },
];
const columns: DataTableColumn<Product>[] = [
{ key: 'name', header: 'Mahsulot', sortable: true },
{ key: 'sku', header: 'Artikul' },
{ key: 'stock', header: 'Qoldiq', numeric: true, sortable: true },
];
export default function DataTableSelection() {
const [products, setProducts] = useState(initial);
const remove = (ids: string[]) => setProducts((prev) => prev.filter((p) => !ids.includes(p.id)));
return (
<DataTable
label="Mahsulotlar"
data={products}
columns={columns}
selectable
className="w-full"
onRowClick={(p) => toast(`${p.name} ochildi`)}
bulkActions={(rows, clear) => (
<>
<Button
variant="outline"
size="sm"
leftIcon={<Download />}
onClick={() => toast(`${rows.length} ta eksport qilindi`)}
>
Eksport
</Button>
<Button
variant="danger"
size="sm"
leftIcon={<Trash2 />}
onClick={() => {
remove(rows.map((r) => r.id));
clear();
}}
>
O'chirish
</Button>
</>
)}
rowActions={(p) => (
<DropdownMenu>
<DropdownMenu.Trigger asChild>
<IconButton variant="ghost" size="sm" aria-label={`${p.name}: amallar`}>
<Ellipsis />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item icon={<Eye />} onSelect={() => toast(p.name)}>
Ko'rish
</DropdownMenu.Item>
<DropdownMenu.Item variant="danger" icon={<Trash2 />} onSelect={() => remove([p.id])}>
O'chirish
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
)}
/>
);
}
<DataTable
data={products}
columns={columns}
selectable
getRowId={(p) => p.sku} // standart — row.id, bo'lmasa tartib raqami
onSelectionChange={(ids) => setSelectedIds(ids)}
bulkActions={(rows, clearSelection) => (
<Button
size="sm"
onClick={() => {
toast(`${rows.length} ta mahsulot arxivlandi`);
clearSelection();
}}
>
Arxivlash
</Button>
)}
/>Tanlash boshqariladigan bo'lishi ham mumkin: selectedIds + onSelectionChange. rowActions —
qator oxiridagi amallar (bosilganda onRowClick ishlamaydi).
Sahifalash
pagination={{ pageSize: 10 }} — pastda "1–10 / 248", sahifa raqamlari va sahifa hajmi tanlovi
(standart variantlar: 10, 25, 50, 100; o'zgartirish — pageSizeOptions). Saralash, qidiruv yoki
filtr o'zgarganda birinchi sahifaga qaytiladi; o'chirishdan keyin sahifa bo'shab qolsa — oxirgi
mavjud sahifaga. pagination bo'lmasa, barcha qatorlar birdaniga chiziladi — ko'p ma'lumot uchun
virtual ni ishlating (u pastda jami qatorlar sonini ko'rsatadi).
Holatlar
| Holat | Ko'rinish |
|---|---|
| Birinchi yuklanish | loading + bo'sh data — Skeleton qatorlar |
| Qayta yuklanish | loading + eski data — 60% shaffof va yuqorida ingichka progress |
| Ma'lumot yo'q | empty — ikonka, sarlavha, izoh, amal tugmasi |
| Hech narsa topilmadi | Qidiruv/filtr natijasi bo'sh — "Filtrlarni tozalash" |
| Xato | error (matn yoki true) + onRetry — "Qayta urinish" tugmasi |
Qayta yuklanishda jadval "sakramaydi" — eski ma'lumot joyida qoladi. Tugmalarni bosib ko'ring:
| № | Mijoz | Summa |
|---|---|---|
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { Button, DataTable, type DataTableColumn } from '@akhatmirzo/ui';
import { Inbox, Plus } from 'lucide-react';
import { useState } from 'react';
interface Order {
id: number;
customer: string;
total: number;
}
const orders: Order[] = [
{ id: 1042, customer: 'Jasur Karimov', total: 1_250_000 },
{ id: 1043, customer: 'Malika Rahimova', total: 480_000 },
{ id: 1044, customer: 'Aziz Normatov', total: 2_730_000 },
];
const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№', width: 88 },
{ key: 'customer', header: 'Mijoz' },
{ key: 'total', header: 'Summa', numeric: true, cell: (o) => formatMoney(o.total) },
];
const STATES = {
data: "Ma'lumot",
first: 'Birinchi yuklanish',
refetch: 'Qayta yuklanish',
empty: "Bo'sh",
error: 'Xato',
} as const;
type State = keyof typeof STATES;
export default function DataTableStates() {
const [state, setState] = useState<State>('first');
return (
<div className="flex w-full flex-col gap-4">
<div className="flex flex-wrap gap-2">
{(Object.keys(STATES) as State[]).map((s) => (
<Button
key={s}
size="sm"
variant={s === state ? 'primary' : 'outline'}
aria-pressed={s === state}
onClick={() => setState(s)}
>
{STATES[s]}
</Button>
))}
</div>
<DataTable
label="Holatlar"
data={state === 'data' || state === 'refetch' ? orders : []}
columns={columns}
columnToggle={false}
loading={state === 'first' || state === 'refetch'}
error={
state === 'error' ? 'Server javob bermadi. Internet aloqasini tekshiring.' : undefined
}
onRetry={() => setState('data')}
empty={{
icon: <Inbox />,
title: "Hali buyurtmalar yo'q",
description: "Birinchi buyurtmani qo'shing — u shu yerda ko'rinadi.",
action: (
<Button size="sm" variant="primary" leftIcon={<Plus />}>
Buyurtma qo'shish
</Button>
),
}}
/>
</div>
);
}
Server rejimi
Ma'lumot serverda saralansa, filtrlansa va sahifalansa — manual bering. Jadval ma'lumotga
tegmaydi: saralash, qidiruv, filtr va sahifa holatini onStateChange orqali beradi, siz shu holat
bilan serverga so'rov yuborasiz. Jami yozuvlar soni — rowCount (sahifalash uchun).
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { DataTable, type DataTableColumn, type DataTableState } from '@akhatmirzo/ui';
import { useEffect, useState } from 'react';
interface Order {
id: number;
customer: string;
total: number;
}
interface Page {
rows: Order[];
total: number;
}
const NAMES = ['Jasur Karimov', 'Malika Rahimova', 'Nodira Saidova', 'Otabek Qodirov'];
const DB: Order[] = Array.from({ length: 137 }, (_, i) => ({
id: 2001 + i,
customer: NAMES[i % NAMES.length]!,
total: 90_000 + ((i * 271_828) % 3_500_000),
}));
/** "Server": qidiradi, saralaydi, sahifalaydi. Haqiqiy loyihada — fetch('/api/orders?...') */
function fetchOrders({ search, sorting, pagination }: DataTableState): Promise<Page> {
let rows = DB.filter((o) => o.customer.toLowerCase().includes(search.toLowerCase()));
const sort = sorting[0];
if (sort) {
const key = sort.id as keyof Order;
rows = [...rows].sort((a, b) => (a[key] > b[key] ? 1 : -1) * (sort.desc ? -1 : 1));
}
const start = pagination.pageIndex * pagination.pageSize;
const page = { rows: rows.slice(start, start + pagination.pageSize), total: rows.length };
return new Promise((resolve) => setTimeout(() => resolve(page), 600));
}
const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№', width: 88, sortable: true },
{ key: 'customer', header: 'Mijoz', sortable: true },
{
key: 'total',
header: 'Summa',
numeric: true,
sortable: true,
cell: (o) => formatMoney(o.total),
},
];
export default function DataTableServer() {
const [query, setQuery] = useState<DataTableState>({
sorting: [],
search: '',
filters: {},
pagination: { pageIndex: 0, pageSize: 10 },
});
const [result, setResult] = useState<Page>({ rows: [], total: 0 });
const [loading, setLoading] = useState(true);
useEffect(() => {
let active = true;
fetchOrders(query).then((page) => {
if (!active) return;
setResult(page);
setLoading(false);
});
return () => {
active = false;
};
}, [query]);
return (
<DataTable
label="Server buyurtmalari"
data={result.rows}
rowCount={result.total}
columns={columns}
manual
searchable
pagination={{ pageSize: 10 }}
loading={loading}
onStateChange={(state) => {
setLoading(true);
setQuery(state);
}}
className="w-full"
/>
);
}
onStateChange ga keladigan holat (DataTableState):
interface DataTableState {
sorting: { id: string; desc: boolean }[]; // id — ustun key'i
search: string; // 300ms debounce'dan keyingi qiymat
filters: Record<string, DataTableFilterValue>; // ustun key'i → filtr qiymati
pagination: { pageIndex: number; pageSize: number }; // pageIndex 0 dan
}Haqiqiy loyihada holatni URL parametrlariga aylantirib, fetch qilasiz:
import { DataTable, type DataTableState } from '@akhatmirzo/ui';
import { useEffect, useState } from 'react';
import { columns, type Order } from './columns';
export function OrdersTable() {
const [state, setState] = useState<DataTableState>({
sorting: [],
search: '',
filters: {},
pagination: { pageIndex: 0, pageSize: 25 },
});
const [page, setPage] = useState<{ rows: Order[]; total: number }>({ rows: [], total: 0 });
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>();
useEffect(() => {
const controller = new AbortController();
const params = new URLSearchParams({
page: String(state.pagination.pageIndex + 1),
size: String(state.pagination.pageSize),
q: state.search,
sort: state.sorting.map((s) => (s.desc ? '-' : '') + s.id).join(','),
filters: JSON.stringify(state.filters),
});
setLoading(true);
fetch(`/api/orders?${params}`, { signal: controller.signal })
.then((res) => res.json())
.then((data: { rows: Order[]; total: number }) => {
setPage(data);
setError(undefined);
setLoading(false);
})
.catch((err: Error) => {
if (err.name === 'AbortError') return;
setError('Server javob bermadi');
setLoading(false);
});
return () => controller.abort();
}, [state]);
return (
<DataTable
label="Buyurtmalar"
data={page.rows}
rowCount={page.total}
columns={columns}
manual
searchable
pagination={{ pageSize: 25 }}
loading={loading}
error={error}
onRetry={() => setState({ ...state })}
onStateChange={setState}
/>
);
}Server rejimida filtr variantlari
manual da jadval faqat joriy sahifani ko'radi, shuning uchun select / multiselect filtrlari
uchun filterOptions ni albatta bering — aks holda variantlar faqat ko'rinib turgan qatorlardan
yig'iladi. Variantlar yonidagi qatorlar soni bu rejimda ko'rsatilmaydi.
Virtual scroll — 10 000 qator
Hamma ma'lumot brauzerda bo'lsa-yu, sahifalash kerak bo'lmasa — virtual. DOM'da faqat
ko'rinadigan qatorlar (va bir oz zaxira, virtual={{ overscan: 10 }}) chiziladi, saralash,
qidiruv va filtr esa butun ma'lumot bo'yicha ishlaydi. Balandlik — maxHeight (standart 480px),
sarlavha yopishqoq. virtual bilan pagination e'tiborsiz qoldiriladi.
| Viloyat | |||
|---|---|---|---|
| 1 | Jasur Karimov | Toshkent | 50 000 so'm |
| 2 | Malika Rahimova | Navoiy | 427 311 so'm |
| 3 | Farhod Tursunov | Samarqand | 804 622 so'm |
| 4 | Mohira Nazarova | Xorazm | 1 181 933 so'm |
| 5 | Jasur Karimov | Buxoro | 1 559 244 so'm |
| 6 | Malika Rahimova | Toshkent | 1 936 555 so'm |
| 7 | Farhod Tursunov | Navoiy | 2 313 866 so'm |
| 8 | Mohira Nazarova | Samarqand | 2 691 177 so'm |
| 9 | Jasur Karimov | Xorazm | 3 068 488 so'm |
| 10 | Malika Rahimova | Buxoro | 3 445 799 so'm |
| 11 | Farhod Tursunov | Toshkent | 3 823 110 so'm |
| 12 | Mohira Nazarova | Navoiy | 4 200 421 so'm |
| 13 | Jasur Karimov | Samarqand | 4 577 732 so'm |
| 14 | Malika Rahimova | Xorazm | 155 043 so'm |
| 15 | Farhod Tursunov | Buxoro | 532 354 so'm |
| 16 | Mohira Nazarova | Toshkent | 909 665 so'm |
| 17 | Jasur Karimov | Navoiy | 1 286 976 so'm |
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { DataTable, type DataTableColumn } from '@akhatmirzo/ui';
import { useMemo } from 'react';
interface Order {
id: number;
customer: string;
region: string;
total: number;
}
const NAMES = ['Jasur Karimov', 'Malika Rahimova', 'Farhod Tursunov', 'Mohira Nazarova'];
const REGIONS = ['Toshkent', 'Samarqand', 'Buxoro', 'Navoiy', 'Xorazm'];
const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№', width: 96, sortable: true },
{ key: 'customer', header: 'Mijoz', sortable: true },
{ key: 'region', header: 'Viloyat', filter: 'select' },
{
key: 'total',
header: 'Summa',
numeric: true,
sortable: true,
cell: (o) => formatMoney(o.total),
},
];
export default function DataTableVirtual() {
const orders = useMemo<Order[]>(
() =>
Array.from({ length: 10_000 }, (_, i) => ({
id: i + 1,
customer: NAMES[i % NAMES.length]!,
region: REGIONS[(i * 3) % REGIONS.length]!,
total: 50_000 + ((i * 377_311) % 4_800_000),
})),
[],
);
return (
<DataTable
label="10 000 buyurtma"
data={orders}
columns={columns}
virtual
maxHeight={400}
density="compact"
searchable
columnToggle={false}
className="w-full"
/>
);
}
Ro'yxat jadval bo'lmasa, xuddi shu texnika — VirtualList.
Zichlik, yopishqoq ustun va ustunlarni yashirish
density="compact"— 40px qatorlar (standartcomfortable— 52px).stickyFirstColumn— gorizontal scroll'da birinchi ustun (va tanlash Checkbox'i) joyida qoladi. Tor ekranda demoni yon tomonga suring.- "Ustunlar" menyusi (
columnToggle, standart yoqiq) ustunlarni ko'rsatadi/yashiradi;hideable: falseustun ro'yxatda chiqmaydi,hidden: true— boshida yashirin. maxHeightberilsa, sarlavha jadval ichida yopishqoq bo'ladi.
| Xodim | Lavozim | Bo'lim | Telefon | Maosh | |
|---|---|---|---|---|---|
| Sarvar Abduxoliqov | Frontend dasturchi | IT | +998 90 123 45 67 | 14 500 000 so'm | |
| Gulnora O'rinova | Buxgalter | Moliya | +998 93 555 12 34 | 9 800 000 so'm | |
| Javohir Ismoilov | Dizayner | Marketing | +998 97 700 00 11 | 11 200 000 so'm |
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { DataTable, type DataTableColumn } from '@akhatmirzo/ui';
interface Employee {
id: number;
name: string;
position: string;
department: string;
phone: string;
city: string;
salary: number;
}
const employees: Employee[] = [
{
id: 1,
name: 'Sarvar Abduxoliqov',
position: 'Frontend dasturchi',
department: 'IT',
phone: '+998 90 123 45 67',
city: 'Toshkent',
salary: 14_500_000,
},
{
id: 2,
name: "Gulnora O'rinova",
position: 'Buxgalter',
department: 'Moliya',
phone: '+998 93 555 12 34',
city: 'Samarqand',
salary: 9_800_000,
},
{
id: 3,
name: 'Javohir Ismoilov',
position: 'Dizayner',
department: 'Marketing',
phone: '+998 97 700 00 11',
city: 'Buxoro',
salary: 11_200_000,
},
];
const columns: DataTableColumn<Employee>[] = [
{ key: 'name', header: 'Xodim', width: 200, hideable: false },
{ key: 'position', header: 'Lavozim' },
{ key: 'department', header: "Bo'lim" },
{ key: 'phone', header: 'Telefon', className: 'tabular-nums' },
{ key: 'city', header: 'Shahar', hidden: true },
{ key: 'salary', header: 'Maosh', numeric: true, cell: (e) => formatMoney(e.salary) },
];
export default function DataTableDensity() {
return (
<DataTable
label="Xodimlar"
data={employees}
columns={columns}
density="compact"
stickyFirstColumn
selectable
tableClassName="min-w-[860px]"
className="w-full"
/>
);
}
Telefonda kartalar
Standart holatda tor ekranda jadval gorizontal scroll bo'ladi. mobileView="cards" — 640px dan
tor ekranda har bir qator karta ko'rinishida (sarlavha: qiymat). Brauzer oynasini toraytirib
ko'ring.
| № | Mijoz | Viloyat | Summa | |
|---|---|---|---|---|
| 1042 | Jasur Karimov | Toshkent | 1 250 000 so'm | |
| 1043 | Malika Rahimova | Samarqand | 480 000 so'm | |
| 1044 | Aziz Normatov | Farg'ona | 2 730 000 so'm |
'use client';
import { formatMoney } from '@akhatmirzo/core';
import { DataTable, toast, type DataTableColumn } from '@akhatmirzo/ui';
interface Order {
id: number;
customer: string;
region: string;
total: number;
}
const orders: Order[] = [
{ id: 1042, customer: 'Jasur Karimov', region: 'Toshkent', total: 1_250_000 },
{ id: 1043, customer: 'Malika Rahimova', region: 'Samarqand', total: 480_000 },
{ id: 1044, customer: 'Aziz Normatov', region: "Farg'ona", total: 2_730_000 },
];
const columns: DataTableColumn<Order>[] = [
{ key: 'id', header: '№' },
{ key: 'customer', header: 'Mijoz' },
{ key: 'region', header: 'Viloyat' },
{ key: 'total', header: 'Summa', numeric: true, cell: (o) => formatMoney(o.total) },
];
export default function DataTableCards() {
return (
<DataTable
label="Buyurtmalar"
data={orders}
columns={columns}
mobileView="cards"
selectable
onRowClick={(o) => toast(`Buyurtma #${o.id}`)}
className="w-full"
/>
);
}
Props
Qolgan <div> atributlarini qabul qiladi (ildizga). className — ildizga, tableClassName —
<table> ga.
Ma'lumot
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
datamajburiy | readonly T[] | — | Qatorlar |
columnsmajburiy | readonly DataTableColumn<T>[] | — | Ustunlar (yuqoridagi jadval) |
getRowId | (row: T, index: number) => string | — | Qator id'si (tanlash uchun). Standart — row.id, bo'lmasa tartib raqami |
label | string | — | Jadval nomi (ekran o'quvchi uchun) |
Imkoniyatlar
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
searchable | boolean | false | Umumiy qidiruv (300ms, o'zbekcha normallashtirish) |
selectable | boolean | false | Checkbox ustuni |
selectedIds | string[] | — | Tanlangan qatorlar (boshqariladigan) |
defaultSelectedIds | string[] | — | |
onSelectionChange | (ids: string[], rows: T[]) => void | — | |
bulkActions | (rows: T[], clearSelection: () => void) => ReactNode | — | Ommaviy amallar paneli |
pagination | false | { pageSize?: number; pageSizeOptions?: number[] } | — | Sahifalash; berilmasa — o'chiq |
virtual | boolean | { overscan?: number } | false | Virtual scroll (sahifalash o'rniga) |
maxHeight | number | string | — | Jadval qismi balandligi; virtual da standart 480. Sarlavha yopishqoq bo'ladi |
columnToggle | boolean | true | "Ustunlar" menyusi |
initialState | Partial<DataTableState> | — | Boshlang'ich saralash, qidiruv, filtr, sahifa |
onRowClick | (row: T) => void | — | Qator bosilganda (yoki Enter) |
rowActions | (row: T) => ReactNode | — | Qator oxiridagi amallar |
toolbar | ReactNode | — | Asboblar panelidagi qo'shimcha elementlar (masalan "Qo'shish") |
Server rejimi
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
manual | boolean | false | Saralash/filtr/sahifa ma'lumotga qo'llanmaydi |
rowCount | number | — | manual da jami yozuvlar soni |
onStateChange | (state: DataTableState) => void | — | Holat o'zgarganda (har qanday rejimda) |
Holatlar va ko'rinish
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
loading | boolean | false | Ma'lumot yo'q — Skeleton; bor — shaffof + progress |
error | ReactNode | — | Xato matni yoki true |
onRetry | () => void | — | "Qayta urinish" tugmasi |
empty | DataTableEmptyState | — | Bo'sh holat: { icon, title, description, action } (hammasi ReactNode) |
density | 'comfortable' | 'compact' | 'comfortable' | Qator balandligi: 52 / 40px |
stickyFirstColumn | boolean | false | |
mobileView | 'table' | 'cards' | 'table' | Telefonda (< 640px) ko'rinish |
labels | DataTableLabels | — | Barcha matnlar |
tableClassName | string | — | <table> klassi |
Matnlar (labels)
Barcha matnlar o'zbekcha standartga ega; faqat kerakli kalitlarni almashtirasiz. Son bilan bog'liqlari — funksiya:
<DataTable
labels={{
search: "Mijoz, № yoki viloyat bo'yicha",
selected: (n) => `${n} ta buyurtma tanlandi`,
range: (from, to, total) => `${from}–${to}, jami ${total}`,
}}
/>Kalitlar: search, searchAria, filters, columns, selectAll, selectRow, selected,
clearSelection, emptyTitle, emptyDescription, noResultsTitle, noResultsDescription,
clearFilters, errorTitle, retry, range, total, noRows, pageSize, prevPage,
nextPage, page, pagination, rowActions, loading, all, min, max, from, to,
removeFilter.
Data atributlari
| Atribut | Element | Qiymat |
|---|---|---|
data-row-id | tr | qator id'si |
data-selected | tr | tanlangan qatorda mavjud |
data-sorted | saralash tugmasi | asc · desc |
data-slot: data-table, data-table-toolbar, data-table-bulk, data-table-scroll,
data-table-footer, data-table-state.
Klaviatura
| Tugma | Harakat |
|---|---|
Tab | Qidiruv, filtrlar, sarlavha tugmalari, qatorlar, amallar |
Enter (sarlavhada) | Saralash: o'sish → kamayish → o'chiq |
Enter (qatorda) | onRowClick |
Space (qatorda) | Qatorni tanlash (selectable) |
Qatorlar faqat onRowClick yoki selectable bo'lganda fokuslanadi.
Accessibility
- Haqiqiy
<table>—<th scope="col">, jadval nomilabelorqali (aria-label). - Saralash holati sarlavhada
aria-sort(ascending/descending/none) bilan; saralanadigan sarlavha —<button>. - Yuklanish — jadvalda
aria-busy, qayta yuklanishdagi chiziq —role="progressbar". - Holat xabarlari (bo'sh, topilmadi) —
role="status", xato —role="alert". - Tanlash: har bir Checkbox'da
"Qatorni tanlash: <id>", sarlavhada "Hammasini tanlash"; "N ta tanlandi"aria-livebilan e'lon qilinadi. - Sahifalash —
<nav aria-label="Sahifalash">, joriy sahifaaria-current="page", "1–10 / 248"aria-livehududida. - Filtrlar: har bir guruh nomlangan, faol filtr badge'ining ✕ tugmasi "Holat filtrini olib tashlash" kabi nomga ega.
- Virtual rejim —
aria-rowcountva har qatordaaria-rowindex: ekran o'quvchi umumiy qatorlar sonini biladi.