@openway/ui 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/chunk-XM7AABEU.js +3 -0
- package/dist/chunk-XM7AABEU.js.map +1 -0
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -348
- package/dist/index.d.ts +160 -348
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +133 -3
- package/dist/query.d.ts +133 -3
- package/dist/query.js +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.css +424 -0
- package/dist/{useInfiniteScroll-DAlgjUN4.d.cts → useInfiniteScroll-C5t7bvLu.d.cts} +313 -10
- package/dist/{useInfiniteScroll-DAlgjUN4.d.ts → useInfiniteScroll-C5t7bvLu.d.ts} +313 -10
- package/docs/AGENTS.md +0 -2
- package/docs/README.md +0 -1
- package/docs/components/carousel.md +5 -5
- package/docs/components/input.md +0 -6
- package/docs/components/select.md +2 -2
- package/docs/components/slider.md +2 -2
- package/docs/components/table.md +114 -1
- package/docs/hooks/useInfiniteScroll.md +1 -1
- package/docs/hooks/useMutationApp.md +242 -0
- package/docs/hooks/useTableQuery.md +5 -0
- package/package.json +3 -2
- package/dist/chunk-CJQNI3QX.js +0 -3
- package/dist/chunk-CJQNI3QX.js.map +0 -1
|
@@ -1,6 +1,279 @@
|
|
|
1
1
|
import { HTMLAttributes, Ref, ReactNode, RefObject } from 'react';
|
|
2
|
+
import { ToasterProps as ToasterProps$1, ToastT } from 'sonner';
|
|
2
3
|
import { Placement } from '@floating-ui/react';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Các kích cỡ tiêu chuẩn của Alert:
|
|
7
|
+
* - 'xs': Siêu nhỏ (text-xs, padding p-2)
|
|
8
|
+
* - 'sm': Nhỏ (text-xs, title text-sm, padding p-2.5)
|
|
9
|
+
* - 'md': Vừa - mặc định (text-sm, padding p-3.5)
|
|
10
|
+
* - 'lg': Lớn (text-base, padding p-4)
|
|
11
|
+
* - 'xl': Rất lớn (text-lg, padding p-5)
|
|
12
|
+
*/
|
|
13
|
+
type AlertSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
14
|
+
/**
|
|
15
|
+
* Các biến thể giao diện và phong cách hiển thị màu sắc của Alert:
|
|
16
|
+
* - 'soft': Nền nhạt pastel, viền mờ 2px, chữ đậm (mặc định)
|
|
17
|
+
* - 'filled': Nền màu đậm, chữ trắng tương phản cao
|
|
18
|
+
* - 'outline': Nền trắng, viền rõ nét theo màu chủ đề
|
|
19
|
+
* - 'accent-left': Nền pastel kèm viền nhấn dày 4px bên trái
|
|
20
|
+
* - 'ghost': Nền và viền trong suốt
|
|
21
|
+
* - 'other': Bỏ qua màu mặc định, tự do styling qua className
|
|
22
|
+
*/
|
|
23
|
+
type AlertVariant = "soft" | "filled" | "outline" | "accent-left" | "ghost" | "other";
|
|
24
|
+
/**
|
|
25
|
+
* 7 chủ đề màu sắc theo Design System:
|
|
26
|
+
* - 'primary': Màu chủ đạo
|
|
27
|
+
* - 'secondary': Màu phụ
|
|
28
|
+
* - 'neutral': Màu trung tính
|
|
29
|
+
* - 'error': Màu đỏ cảnh báo lỗi nguy cấp
|
|
30
|
+
* - 'success': Màu xanh lá thông báo thành công
|
|
31
|
+
* - 'warning': Màu vàng/cam cảnh báo
|
|
32
|
+
* - 'info': Màu xanh dương thông tin (mặc định)
|
|
33
|
+
*/
|
|
34
|
+
type AlertColor = "primary" | "secondary" | "neutral" | "error" | "success" | "warning" | "info";
|
|
35
|
+
/**
|
|
36
|
+
* Độ bo góc của khung thông báo Alert:
|
|
37
|
+
* - 'none': Góc vuông phẳng (rounded-none)
|
|
38
|
+
* - 'sm': Bo góc nhỏ (rounded-sm)
|
|
39
|
+
* - 'md': Bo góc vừa (rounded-md)
|
|
40
|
+
* - 'lg': Bo góc lớn (rounded-lg - mặc định)
|
|
41
|
+
* - 'xl': Bo góc rất lớn (rounded-xl)
|
|
42
|
+
* - 'full': Bo tròn hoàn toàn (rounded-2xl)
|
|
43
|
+
*/
|
|
44
|
+
type AlertRadius = "none" | "sm" | "md" | "lg" | "xl" | "full";
|
|
45
|
+
/**
|
|
46
|
+
* Props cho component `Alert`.
|
|
47
|
+
*/
|
|
48
|
+
interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
49
|
+
/**
|
|
50
|
+
* Ref chuyển tiếp đến phần tử HTML div của Alert
|
|
51
|
+
*/
|
|
52
|
+
ref?: Ref<HTMLDivElement>;
|
|
53
|
+
/**
|
|
54
|
+
* Kích cỡ của Alert (5 sizes: xs, sm, md, lg, xl)
|
|
55
|
+
* @default 'md'
|
|
56
|
+
*/
|
|
57
|
+
size?: AlertSize;
|
|
58
|
+
/**
|
|
59
|
+
* Biến thể giao diện của Alert:
|
|
60
|
+
* - 'soft': nền nhạt pastel, viền mờ, chữ đậm tương ứng (mặc định)
|
|
61
|
+
* - 'filled': nền màu đậm, chữ tương phản cao
|
|
62
|
+
* - 'outline': nền trắng, viền rõ nét theo màu chủ đề
|
|
63
|
+
* - 'accent-left': viền nhấn dày bên trái, nền pastel
|
|
64
|
+
* - 'ghost': nền và viền trong suốt
|
|
65
|
+
* - 'other': không áp dụng style màu mặc định, tự do tùy biến qua className
|
|
66
|
+
* @default 'soft'
|
|
67
|
+
*/
|
|
68
|
+
variant?: AlertVariant;
|
|
69
|
+
/**
|
|
70
|
+
* Chủ đề màu sắc (primary, secondary, neutral, error, success, warning, info)
|
|
71
|
+
* @default 'info'
|
|
72
|
+
*/
|
|
73
|
+
color?: AlertColor;
|
|
74
|
+
/**
|
|
75
|
+
* Tùy chỉnh độ bo góc:
|
|
76
|
+
* - 'none': góc vuông 0px
|
|
77
|
+
* - 'sm': bo góc nhỏ
|
|
78
|
+
* - 'md': bo góc vừa
|
|
79
|
+
* - 'lg': bo góc lớn (mặc định)
|
|
80
|
+
* - 'xl': bo góc rất lớn
|
|
81
|
+
* - 'full': bo tròn hoàn toàn
|
|
82
|
+
* @default 'lg'
|
|
83
|
+
*/
|
|
84
|
+
radius?: AlertRadius;
|
|
85
|
+
/**
|
|
86
|
+
* Tiêu đề của Alert
|
|
87
|
+
*/
|
|
88
|
+
title?: ReactNode;
|
|
89
|
+
/**
|
|
90
|
+
* Nội dung mô tả chi tiết của Alert (có thể truyền qua prop description hoặc children)
|
|
91
|
+
*/
|
|
92
|
+
description?: ReactNode;
|
|
93
|
+
/**
|
|
94
|
+
* Icon hiển thị ở đầu thông báo:
|
|
95
|
+
* - `true` (hoặc không truyền): tự động hiển thị icon theo `color`
|
|
96
|
+
* - `false`: ẩn icon
|
|
97
|
+
* - `ReactNode`: icon tùy chỉnh
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
icon?: ReactNode | boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Phần tử hành động phụ (Action slot, ví dụ: Nút bấm, liên kết thao tác)
|
|
103
|
+
*/
|
|
104
|
+
action?: ReactNode;
|
|
105
|
+
/**
|
|
106
|
+
* Hiển thị nút đóng (Close button x) để người dùng có thể tắt thông báo. Tự động ẩn Alert khi click.
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
closable?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Callback khi người dùng bấm nút đóng
|
|
112
|
+
*/
|
|
113
|
+
onClose?: () => void;
|
|
114
|
+
/**
|
|
115
|
+
* Nhãn accessibility cho nút đóng
|
|
116
|
+
* @default 'Close alert'
|
|
117
|
+
*/
|
|
118
|
+
closeAriaLabel?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Chế độ Banner: Full width, không bo góc (`rounded-none`), không viền 2 bên (`border-x-0`), thích hợp gắn cố định trên cùng màn hình
|
|
121
|
+
* @default false
|
|
122
|
+
*/
|
|
123
|
+
banner?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Class tùy biến cho phần tiêu đề (title)
|
|
126
|
+
*/
|
|
127
|
+
titleClassName?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Class tùy biến cho phần nội dung mô tả (description / children)
|
|
130
|
+
*/
|
|
131
|
+
descriptionClassName?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Class tùy biến cho vùng action slot
|
|
134
|
+
*/
|
|
135
|
+
actionClassName?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Class tùy biến cho icon wrapper
|
|
138
|
+
*/
|
|
139
|
+
iconClassName?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Class tùy biến cho nút đóng
|
|
142
|
+
*/
|
|
143
|
+
closeButtonClassName?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Class tùy biến cho container bao ngoài
|
|
146
|
+
*/
|
|
147
|
+
className?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Nội dung tùy biến bên trong Alert
|
|
150
|
+
*/
|
|
151
|
+
children?: ReactNode;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Vị trí hiển thị của danh sách thông báo Toast trên màn hình.
|
|
156
|
+
*/
|
|
157
|
+
type ToastPosition = "top-left" | "top-right" | "top-center" | "bottom-left" | "bottom-right" | "bottom-center";
|
|
158
|
+
/**
|
|
159
|
+
* Cấu hình tùy chọn cho từng thông báo Toast đơn lẻ:
|
|
160
|
+
* - Dạng gọi hàm chuẩn: `toast.success(title, description?, options?)`
|
|
161
|
+
*/
|
|
162
|
+
interface ToastOptions extends Omit<AlertProps, "title" | "description"> {
|
|
163
|
+
/**
|
|
164
|
+
* Thời gian hiển thị thông báo trước khi tự động đóng (miliseconds). Mặc định 4000ms.
|
|
165
|
+
* @default 4000
|
|
166
|
+
*/
|
|
167
|
+
duration?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Vị trí hiển thị riêng cho thông báo này trên màn hình.
|
|
170
|
+
*/
|
|
171
|
+
position?: ToastPosition;
|
|
172
|
+
/**
|
|
173
|
+
* Callback được gọi khi thông báo bị đóng/gỡ bỏ khỏi màn hình.
|
|
174
|
+
*/
|
|
175
|
+
onDismiss?: (toast: ToastT) => void;
|
|
176
|
+
/**
|
|
177
|
+
* Callback được gọi khi thông báo tự động đóng do hết thời gian duration.
|
|
178
|
+
*/
|
|
179
|
+
onAutoClose?: (toast: ToastT) => void;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Cấu trúc thông báo dạng Object chi tiết cho `toast.promise`.
|
|
183
|
+
*/
|
|
184
|
+
interface ToastMessageObject {
|
|
185
|
+
title?: ReactNode;
|
|
186
|
+
description?: ReactNode;
|
|
187
|
+
color?: AlertColor;
|
|
188
|
+
variant?: AlertVariant;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Kiểu dữ liệu linh hoạt cho nội dung thông báo trả về trong `toast.promise`:
|
|
192
|
+
* - `ReactNode`: Chuỗi text hoặc phần tử JSX thông thường.
|
|
193
|
+
* - `ToastMessageObject`: Object chi tiết gồm `{ title, description, color, variant }`.
|
|
194
|
+
*/
|
|
195
|
+
type ToastMessageResult = ReactNode | ToastMessageObject;
|
|
196
|
+
/**
|
|
197
|
+
* Hàm tạo nội dung thông báo thành công động dựa theo dữ liệu `data: T` trả về từ Promise.
|
|
198
|
+
*/
|
|
199
|
+
type ToastPromiseFunction<T> = (data: T) => ToastMessageResult;
|
|
200
|
+
/**
|
|
201
|
+
* Cấu hình các trạng thái cho `toast.promise(promise, options)`.
|
|
202
|
+
*/
|
|
203
|
+
interface ToastPromiseOptions<T = unknown> {
|
|
204
|
+
/**
|
|
205
|
+
* Nội dung hiển thị trong lúc Promise đang thực thi (Loading state).
|
|
206
|
+
*/
|
|
207
|
+
loading: ToastMessageResult;
|
|
208
|
+
/**
|
|
209
|
+
* Nội dung hiển thị khi Promise thực thi thành công (Resolve state).
|
|
210
|
+
* Có thể truyền trực tiếp nội dung hoặc một hàm nhận `data: T`.
|
|
211
|
+
*/
|
|
212
|
+
success: ToastMessageResult | ToastPromiseFunction<T>;
|
|
213
|
+
/**
|
|
214
|
+
* Nội dung hiển thị khi Promise gặp lỗi (Reject state).
|
|
215
|
+
* Có thể truyền trực tiếp nội dung hoặc một hàm nhận `error: unknown`.
|
|
216
|
+
*/
|
|
217
|
+
error: ToastMessageResult | ((error: unknown) => ToastMessageResult);
|
|
218
|
+
/**
|
|
219
|
+
* Callback chạy sau khi Promise hoàn tất (cả khi resolve hoặc reject).
|
|
220
|
+
*/
|
|
221
|
+
finally?: () => void | Promise<void>;
|
|
222
|
+
/**
|
|
223
|
+
* Thời gian hiển thị thông báo kết quả (ms). Mặc định 4000ms.
|
|
224
|
+
* @default 4000
|
|
225
|
+
*/
|
|
226
|
+
duration?: number;
|
|
227
|
+
/**
|
|
228
|
+
* Kích cỡ của Alert hiển thị trong Toast.
|
|
229
|
+
*/
|
|
230
|
+
size?: AlertSize;
|
|
231
|
+
/**
|
|
232
|
+
* Biến thể giao diện của Alert hiển thị trong Toast.
|
|
233
|
+
*/
|
|
234
|
+
variant?: AlertVariant;
|
|
235
|
+
/**
|
|
236
|
+
* Tùy chỉnh độ bo góc của Alert hiển thị trong Toast.
|
|
237
|
+
*/
|
|
238
|
+
radius?: AlertRadius;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Props cho component `<Toaster />` được mount tại root layout của ứng dụng.
|
|
242
|
+
*/
|
|
243
|
+
interface ToasterProps extends ToasterProps$1 {
|
|
244
|
+
/**
|
|
245
|
+
* Vị trí mặc định của toàn bộ danh sách toasts trên màn hình.
|
|
246
|
+
* @default 'top-right'
|
|
247
|
+
*/
|
|
248
|
+
position?: ToastPosition;
|
|
249
|
+
/**
|
|
250
|
+
* Số lượng toast tối đa hiển thị cùng lúc trước khi tự động xếp chồng 3D (stack).
|
|
251
|
+
* @default 3
|
|
252
|
+
*/
|
|
253
|
+
visibleToasts?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Mở rộng toàn bộ danh sách toasts thay vì gộp lại thành 1 stack 3D.
|
|
256
|
+
* - `false` (mặc định): Tự động gộp thẻ 3D, chỉ bung ra khi hover chuột.
|
|
257
|
+
* - `true`: Luôn luôn mở rộng trải dài toàn bộ danh sách.
|
|
258
|
+
* @default false
|
|
259
|
+
*/
|
|
260
|
+
expand?: boolean;
|
|
261
|
+
/**
|
|
262
|
+
* Thời gian hiển thị mặc định của các toasts (miliseconds).
|
|
263
|
+
* @default 4000
|
|
264
|
+
*/
|
|
265
|
+
duration?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Cho phép hiển thị nút đóng `(X)` trên các toasts.
|
|
268
|
+
* @default true
|
|
269
|
+
*/
|
|
270
|
+
closeButton?: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Tùy biến className bao ngoài container của Toaster.
|
|
273
|
+
*/
|
|
274
|
+
className?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
4
277
|
type EmptySize = "sm" | "md" | "lg";
|
|
5
278
|
type EmptyPresetImage = "default" | "search" | "error" | "folder" | "simple";
|
|
6
279
|
type EmptyLayout = "vertical" | "horizontal";
|
|
@@ -121,9 +394,43 @@ interface SelectFilterField<TValue = unknown> {
|
|
|
121
394
|
onChange: (val: TValue) => void;
|
|
122
395
|
}) => ReactNode;
|
|
123
396
|
}
|
|
397
|
+
interface SelectConfig {
|
|
398
|
+
/**
|
|
399
|
+
* Đánh dấu trường bắt buộc nhập (hiển thị dấu * đỏ cạnh label)
|
|
400
|
+
* @default false
|
|
401
|
+
*/
|
|
402
|
+
isRequired?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Trạng thái báo lỗi (viền đỏ)
|
|
405
|
+
* @default false
|
|
406
|
+
*/
|
|
407
|
+
isInvalid?: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Trạng thái đang tải dữ liệu
|
|
410
|
+
* @default false
|
|
411
|
+
*/
|
|
412
|
+
isLoading?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Hiển thị biểu tượng xoay spinner khi đang ở trạng thái loading
|
|
415
|
+
* @default false
|
|
416
|
+
*/
|
|
417
|
+
showSpinner?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Hiển thị nút xóa nhanh nội dung khi select có giá trị
|
|
420
|
+
* @default false
|
|
421
|
+
*/
|
|
422
|
+
isClearable?: boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Mở rộng chiều rộng 100% của container chứa
|
|
425
|
+
* @default true
|
|
426
|
+
*/
|
|
427
|
+
isFullWidth?: boolean;
|
|
428
|
+
}
|
|
124
429
|
interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> {
|
|
125
430
|
/** Ref chuyển tiếp tới root wrapper element */
|
|
126
431
|
ref?: Ref<HTMLDivElement>;
|
|
432
|
+
/** Cấu hình tập trung các cờ trạng thái / tính năng */
|
|
433
|
+
config?: SelectConfig;
|
|
127
434
|
/** Kích thước: xs, sm, md, lg, xl. Mặc định 'md' */
|
|
128
435
|
size?: SelectSize;
|
|
129
436
|
/** Biến thể giao diện: outline, filled, ghost, other. Mặc định 'outline' */
|
|
@@ -138,7 +445,7 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
138
445
|
id?: string;
|
|
139
446
|
/** Nhãn tiêu đề của Select */
|
|
140
447
|
label?: ReactNode;
|
|
141
|
-
/** Vị trí nhãn: top, left, floating. Mặc định '
|
|
448
|
+
/** Vị trí nhãn: top, left, floating. Mặc định 'floating' */
|
|
142
449
|
labelPlacement?: LabelPlacement;
|
|
143
450
|
/** ClassName tùy biến cho nhãn */
|
|
144
451
|
labelClassName?: string;
|
|
@@ -148,10 +455,6 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
148
455
|
helperText?: ReactNode;
|
|
149
456
|
/** Thông báo lỗi khi không hợp lệ */
|
|
150
457
|
errorMessage?: ReactNode;
|
|
151
|
-
/** Trạng thái không hợp lệ */
|
|
152
|
-
isInvalid?: boolean;
|
|
153
|
-
/** Bắt buộc chọn */
|
|
154
|
-
isRequired?: boolean;
|
|
155
458
|
/** Vô hiệu hóa Select */
|
|
156
459
|
isDisabled?: boolean;
|
|
157
460
|
/** Chỉ đọc, không cho phép thay đổi */
|
|
@@ -198,16 +501,14 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
198
501
|
debounceMs?: number;
|
|
199
502
|
/** Hàm custom predicate lọc options ở chế độ client */
|
|
200
503
|
filterFn?: (option: SelectOptionItem<TData>, query: string, filters: TFilters) => boolean;
|
|
201
|
-
/** Trạng thái đang tải dữ liệu */
|
|
202
|
-
isLoading?: boolean;
|
|
203
504
|
/** Văn bản hiển thị khi không có dữ liệu */
|
|
204
505
|
emptyText?: ReactNode;
|
|
205
506
|
/** Props tùy biến chuyển tiếp đến component Empty */
|
|
206
507
|
emptyProps?: Partial<EmptyProps>;
|
|
207
|
-
/** Hiển thị nút xoá nhanh toàn bộ giá trị đã chọn */
|
|
208
|
-
clearable?: boolean;
|
|
209
508
|
/** Render menu qua Portal để tránh bị che bởi overflow hidden. Mặc định true */
|
|
210
509
|
portal?: boolean;
|
|
510
|
+
/** Container để gắn portal menu vào (mặc định document.body, tự động nhận diện `<dialog>` nếu Select nằm trong Modal/Confirm) */
|
|
511
|
+
portalRoot?: HTMLElement | null | React.RefObject<HTMLElement | null>;
|
|
211
512
|
/** Vị trí hiển thị của popup dropdown. Mặc định 'bottom-start' */
|
|
212
513
|
placement?: Placement;
|
|
213
514
|
/** Chiều cao tối đa của vùng cuộn menu. Mặc định 280px */
|
|
@@ -231,6 +532,8 @@ interface BaseSelectProps<TData = unknown, TFilters extends Record<string, unkno
|
|
|
231
532
|
triggerClassName?: string;
|
|
232
533
|
/** ClassName cho popup dropdown menu */
|
|
233
534
|
menuClassName?: string;
|
|
535
|
+
/** ClassName cho phần thông báo helper/error text */
|
|
536
|
+
helperClassName?: string;
|
|
234
537
|
}
|
|
235
538
|
/** Props cho component Select (Single Select) */
|
|
236
539
|
interface SelectProps<TData = unknown, TFilters extends Record<string, unknown> = Record<string, unknown>> extends BaseSelectProps<TData, TFilters> {
|
|
@@ -322,4 +625,4 @@ interface UseInfiniteScrollReturn {
|
|
|
322
625
|
*/
|
|
323
626
|
declare function useInfiniteScroll({ onLoadMore, hasMore, isLoading, disabled, rootMargin, threshold, root, }: UseInfiniteScrollOptions): UseInfiniteScrollReturn;
|
|
324
627
|
|
|
325
|
-
export { type BaseSelectProps as B, type EmptyProps as E, type MultiSelectProps as M, type SelectProps as S, type UseInfiniteScrollOptions as U, type
|
|
628
|
+
export { type AlertProps as A, type BaseSelectProps as B, type EmptyProps as E, type MultiSelectProps as M, type SelectProps as S, type ToasterProps as T, type UseInfiniteScrollOptions as U, type ToastOptions as a, type ToastPromiseOptions as b, type EmptyPresetImage as c, type SelectOptionItem as d, type SelectSize as e, type SelectVariant as f, type SelectColor as g, type SelectRadius as h, type SelectSearchPlacement as i, type SelectFilterField as j, type SelectFilterLayout as k, type AlertColor as l, type AlertRadius as m, type AlertSize as n, type AlertVariant as o, type EmptyLayout as p, type EmptySize as q, type SelectFilterType as r, type SelectSearchMode as s, type SingleSelectProps as t, type ToastMessageObject as u, type ToastMessageResult as v, type ToastPosition as w, type ToastPromiseFunction as x, type UseInfiniteScrollReturn as y, useInfiniteScroll as z };
|
package/docs/AGENTS.md
CHANGED
|
@@ -131,8 +131,6 @@ Tất cả tài liệu chi tiết của từng component được lưu trữ t
|
|
|
131
131
|
- `xs` (24px), `sm` (32px), `md` (40px - chuẩn cho form input/nút), `lg` (48px), `xl` (56px).
|
|
132
132
|
3. **Bo góc (`radius`)**:
|
|
133
133
|
- `none` (0px), `sm` (4px), `md` (6px), `lg` (8px - chuẩn mặc định), `xl` (12px), `full` (tròn).
|
|
134
|
-
4. **Hỗ trợ chế độ Tối (Dark Mode)**:
|
|
135
|
-
- Tất cả component tự động thích ứng qua class Tailwind `dark:`.
|
|
136
134
|
|
|
137
135
|
---
|
|
138
136
|
|
package/docs/README.md
CHANGED
|
@@ -147,4 +147,3 @@ Tài liệu chi tiết của các hooks được lưu trữ tại thư mục [`h
|
|
|
147
147
|
1. **Zero `any`**: 100% các component và hook đều được khai báo TypeScript chặt chẽ, type-safe.
|
|
148
148
|
2. **5 Kích thước Chuẩn (`size`)**: `xs` (24px), `sm` (32px), `md` (40px - chuẩn form), `lg` (48px), `xl` (56px).
|
|
149
149
|
3. **Độ Bo Góc Chuẩn (`radius`)**: `none` (0px), `sm` (4px), `md` (6px), `lg` (8px - chuẩn mặc định), `xl` (12px), `full` (tròn).
|
|
150
|
-
4. **Dark Mode**: Toàn bộ component hỗ trợ tự động thích ứng qua selector `dark:`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# 🎠 Carousel Component (`@owa/ui`)
|
|
2
2
|
|
|
3
3
|
Component **Carousel** (băng chuyền / slider) hiện đại, hiệu năng cao, thiết kế chuẩn **Compound Components Pattern** (`<Carousel>`, `<CarouselContent>`, `<CarouselSlide>`, `<CarouselPrevious>`, `<CarouselNext>`, `<CarouselPagination>`), hỗ trợ **Pointer Drag/Touch Gestures**, **Autoplay thông minh**, **Infinite Looping**, **Glassmorphism Design**, **Dynamic Slide Registration**, **Safe Config Fallback** (`getSafeConfig`) và tuân thủ đầy đủ tiêu chuẩn **WAI-ARIA Accessibility**.
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ Component **Carousel** (băng chuyền / slider) hiện đại, hiệu năng cao
|
|
|
12
12
|
- **Autoplay thông minh**: Tự động chuyển slide theo chu kỳ `interval`, tự động tạm dừng khi rê chuột (`pauseOnHover`), khi focus (`pauseOnFocus`), hoặc khi đang kéo chuột/vuốt màn hình.
|
|
13
13
|
- **Vòng lặp vô hạn (Infinite Loop)**: Tự động tính toán chuyển động vòng tròn liền mạch giữa slide đầu và slide cuối.
|
|
14
14
|
- **Hiển thị nhiều slide cùng lúc (Multi-slides)**: Hỗ trợ `slidesToShow`, `slidesToScroll`, và khoảng cách gap `spacing` linh hoạt (nhận `number` theo px hoặc chuỗi CSS).
|
|
15
|
-
- **Thiết kế Kính Mờ (Frosted Glass / Glassmorphism)**: Phong cách nút và thanh phân trang kính mờ hiện đại với `backdrop-blur-md
|
|
15
|
+
- **Thiết kế Kính Mờ (Frosted Glass / Glassmorphism)**: Phong cách nút và thanh phân trang kính mờ hiện đại với `backdrop-blur-md`.
|
|
16
16
|
- **Định vị mặc định thông minh**:
|
|
17
17
|
- `CarouselPrevious`: Nút lùi kính mờ đặt sát mép trái (`absolute left-2 top-1/2 -translate-y-1/2`).
|
|
18
18
|
- `CarouselNext`: Nút tiến kính mờ đặt sát mép phải (`absolute right-2 top-1/2 -translate-y-1/2`).
|
|
@@ -151,9 +151,9 @@ export function BasicCarousel() {
|
|
|
151
151
|
<CarouselContent>
|
|
152
152
|
{products.map((product) => (
|
|
153
153
|
<CarouselSlide key={product.id}>
|
|
154
|
-
<div className="p-4 border border-neutral-200
|
|
154
|
+
<div className="p-4 border border-neutral-200 rounded-xl bg-white shadow-sm">
|
|
155
155
|
<img src={product.image} alt={product.name} className="h-40 w-full object-cover rounded-lg" />
|
|
156
|
-
<h3 className="mt-2 font-semibold text-neutral-900
|
|
156
|
+
<h3 className="mt-2 font-semibold text-neutral-900">{product.name}</h3>
|
|
157
157
|
<p className="text-primary-600 font-bold">{product.price}</p>
|
|
158
158
|
</div>
|
|
159
159
|
</CarouselSlide>
|
|
@@ -205,7 +205,7 @@ Hỗ trợ 4 biến thể giao diện: `glass` (*mặc định*), `filled`, `out
|
|
|
205
205
|
Bạn có thể dễ dàng đặt nút điều hướng và phân trang vào trong một thanh công cụ tùy biến dưới đáy bằng cách thêm `className="static"` hoặc class định vị mong muốn:
|
|
206
206
|
|
|
207
207
|
```tsx
|
|
208
|
-
<Carousel defaultIndex={1} className="w-full max-w-md border border-neutral-200
|
|
208
|
+
<Carousel defaultIndex={1} className="w-full max-w-md border border-neutral-200 p-2 rounded-xl">
|
|
209
209
|
<CarouselContent>
|
|
210
210
|
<CarouselSlide><div className="h-36 bg-linear-to-r from-primary-600 to-primary-800 text-white rounded-lg flex items-center justify-center font-semibold">Slide 1</div></CarouselSlide>
|
|
211
211
|
<CarouselSlide><div className="h-36 bg-linear-to-r from-primary-700 to-primary-900 text-white rounded-lg flex items-center justify-center font-semibold">Slide 2</div></CarouselSlide>
|
package/docs/components/input.md
CHANGED
|
@@ -162,19 +162,13 @@ export function BasicInputExample() {
|
|
|
162
162
|
| `label` | `ReactNode` | — | Nhãn tiêu đề hiển thị cho ô nhập liệu. |
|
|
163
163
|
| `labelPlacement` | `'floating' \| 'top' \| 'left'` | `'floating'` | Vị trí hiển thị của nhãn. |
|
|
164
164
|
| `config` | `InputConfig` | — | Cấu hình tập trung các cờ trạng thái / tính năng (`isRequired`, `isInvalid`, `isLoading`, `showSpinner`, `isClearable`, `isFullWidth`). |
|
|
165
|
-
| `isRequired` | `boolean` | `false` | Hiển thị dấu sao đỏ `*` và đánh dấu `aria-required="true"`. |
|
|
166
165
|
| `helperText` | `ReactNode` | — | Đoạn văn bản hướng dẫn/trợ giúp bên dưới ô. |
|
|
167
166
|
| `errorMessage` | `ReactNode` | — | Thông báo lỗi khi nhập sai (tự kích hoạt trạng thái báo lỗi). |
|
|
168
|
-
| `isInvalid` | `boolean` | `false` | Bật trạng thái viền đỏ báo lỗi và `aria-invalid="true"`. |
|
|
169
|
-
| `isLoading` | `boolean` | `false` | Khóa tương tác, bật `aria-busy="true"` và `aria-disabled="true"`. |
|
|
170
|
-
| `showSpinner` | `boolean` | `false` | Hiển thị biểu tượng xoay spinner khi `isLoading={true}`. |
|
|
171
167
|
| `leftIcon` | `ReactNode` | — | Icon hiển thị ở đầu ô input. |
|
|
172
168
|
| `rightIcon` | `ReactNode` | — | Icon hiển thị ở cuối ô input. |
|
|
173
169
|
| `leftAddon` | `ReactNode` | — | Addon / prefix cố định ở đầu ô input (vd: `'https://'`). |
|
|
174
170
|
| `rightAddon` | `ReactNode` | — | Addon / suffix cố định ở cuối ô input (vd: `'.com'`). |
|
|
175
|
-
| `isClearable` | `boolean` | `false` | Hiển thị nút xóa nhanh nội dung khi có văn bản. |
|
|
176
171
|
| `onClear` | `() => void` | — | Callback được gọi khi bấm nút xóa nhanh. |
|
|
177
|
-
| `isFullWidth` | `boolean` | `false` | Mở rộng chiếm toàn bộ 100% chiều ngang container cha. |
|
|
178
172
|
| `ref` | `Ref<HTMLInputElement>` | — | Ref chuyển tiếp đến thẻ `<input>` HTML bên dưới. |
|
|
179
173
|
|
|
180
174
|
---
|
|
@@ -187,10 +187,10 @@ export function ServerInfiniteSelectExample() {
|
|
|
187
187
|
className="w-7 h-7 rounded-full object-cover"
|
|
188
188
|
/>
|
|
189
189
|
<div>
|
|
190
|
-
<div className="font-medium text-sm text-neutral-800
|
|
190
|
+
<div className="font-medium text-sm text-neutral-800">
|
|
191
191
|
{option.label}
|
|
192
192
|
</div>
|
|
193
|
-
<div className="text-xs text-neutral-400
|
|
193
|
+
<div className="text-xs text-neutral-400">
|
|
194
194
|
{option.description}
|
|
195
195
|
</div>
|
|
196
196
|
</div>
|
|
@@ -159,8 +159,8 @@ Hỗ trợ 5 kích thước: `xs`, `sm`, `md` (*mặc định*), `lg`, `xl`:
|
|
|
159
159
|
variant="other"
|
|
160
160
|
label="Custom Other"
|
|
161
161
|
defaultValue={60}
|
|
162
|
-
trackClassName="bg-purple-200
|
|
163
|
-
fillerClassName="bg-purple-600
|
|
162
|
+
trackClassName="bg-purple-200"
|
|
163
|
+
fillerClassName="bg-purple-600"
|
|
164
164
|
thumbClassName="border-purple-600"
|
|
165
165
|
/>
|
|
166
166
|
```
|
package/docs/components/table.md
CHANGED
|
@@ -19,8 +19,13 @@ Bộ component **Table** và **DataTable** chuyên nghiệp, tích hợp sâu v
|
|
|
19
19
|
- 📄 **Phân trang chuyên nghiệp (Pagination)**: Điều hướng trang đầu, trang trước, trang sau, trang cuối; chọn số dòng mỗi trang (10, 20, 50, 100).
|
|
20
20
|
- ☑️ **Chọn dòng (Row Selection)**: Tự động render checkbox chọn từng dòng hoặc chọn toàn bộ dòng trên trang, kèm thanh hiển thị số lượng dòng đã chọn và khu vực Bulk Actions.
|
|
21
21
|
- 👁️ **Ẩn/hiện cột (Column Visibility)**: Dropdown menu popover cho phép người dùng chủ động chọn cột cần xem.
|
|
22
|
+
- 🌲 **Mở rộng dòng & Dữ liệu cây (Row Expanding & Tree Data)**:
|
|
23
|
+
- Hỗ trợ mở rộng xem chi tiết phụ (Detail Panel / Sub-component) gộp toàn bộ cột (`renderExpandedRow`), dễ dàng tích hợp `useQuery` để fetch dữ liệu chi tiết.
|
|
24
|
+
- Hỗ trợ dữ liệu cây phân cấp đệ quy nhiều tầng (Cha ➔ Con ➔ Cháu...) hiển thị CÙNG HÀNG & CÙNG CỘT với bảng chính (`getSubRows`).
|
|
25
|
+
- Nút Chevron mở rộng tự động với animation xoay mượt mà, nút mở rộng/thu gọn tất cả trên header.
|
|
26
|
+
- Tự động thụt đầu dòng theo cấp độ sâu (`row.depth`), tích hợp cơ chế khống chế mức thụt tối đa (`maxIndentDepth`) để bảo vệ layout.
|
|
22
27
|
- ⏳ **Trạng thái tải & Rỗng thông minh**: Tự động render Skeleton rows khi `isLoading={true}` và hiển thị `<Empty>` minh họa khi bảng không có dữ liệu hoặc không tìm thấy kết quả tìm kiếm.
|
|
23
|
-
- 🌐 **Hỗ trợ cả Client-side & Server-side (Manual Mode)**: Linh hoạt kết nối API phân trang, tìm kiếm từ máy chủ với `manualPagination`, `manualSorting`, `pageCount`, `rowCount`.
|
|
28
|
+
- 🌐 **Hỗ trợ cả Client-side & Server-side (Manual Mode)**: Linh hoạt kết nối API phân trang, tìm kiếm từ máy chủ với `manualPagination`, `manualSorting`, `manualExpanding`, `pageCount`, `rowCount`.
|
|
24
29
|
- **3 Kích cỡ hiển thị (`size`)**: `sm` (gọn gàng, dense), `md` (chuẩn - *mặc định*), `lg` (thoáng đãng).
|
|
25
30
|
- **3 Biến thể giao diện (`variant`)**: `default` (viền thanh lịch), `striped` (xen kẽ màu dòng), `bordered` (viền ô đầy đủ).
|
|
26
31
|
|
|
@@ -58,6 +63,7 @@ import type {
|
|
|
58
63
|
ColumnOrderState,
|
|
59
64
|
TablePaginationProps,
|
|
60
65
|
TableToolbarProps,
|
|
66
|
+
ExpandedState,
|
|
61
67
|
} from "@openway/ui";
|
|
62
68
|
```
|
|
63
69
|
|
|
@@ -254,6 +260,97 @@ export function ServerSideTableExample() {
|
|
|
254
260
|
|
|
255
261
|
---
|
|
256
262
|
|
|
263
|
+
### 5. Mở rộng xem chi tiết phụ (Detail Panel với `renderExpandedRow` & `useQuery`)
|
|
264
|
+
|
|
265
|
+
Khi người dùng bấm nút mở rộng dòng, bạn có thể render một component con tùy biến. Bên trong component con có thể sử dụng trực tiếp hook `useQuery` của TanStack Query để lấy dữ liệu chi tiết:
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { useQuery } from "@tanstack/react-query";
|
|
269
|
+
import { DataTable, Skeleton } from "@openway/ui";
|
|
270
|
+
|
|
271
|
+
function OrderDetailPanel({ orderId }: { orderId: string }) {
|
|
272
|
+
// useQuery chỉ chạy khi dòng cha được bấm mở rộng (component mount)
|
|
273
|
+
const { data: orderDetail, isLoading } = useQuery({
|
|
274
|
+
queryKey: ["order-detail", orderId],
|
|
275
|
+
queryFn: () => fetch(`/api/orders/${orderId}`).then((res) => res.json()),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (isLoading) {
|
|
279
|
+
return (
|
|
280
|
+
<div className="p-3">
|
|
281
|
+
<Skeleton className="h-10 w-full rounded" />
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div className="p-4 bg-neutral-50 rounded">
|
|
288
|
+
<h4 className="font-semibold text-neutral-800">Chi tiết đơn hàng #{orderId}</h4>
|
|
289
|
+
<p className="text-sm text-neutral-600">Địa chỉ giao hàng: {orderDetail?.shippingAddress}</p>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function ExpandableDetailTableExample() {
|
|
295
|
+
return (
|
|
296
|
+
<DataTable
|
|
297
|
+
columns={columns}
|
|
298
|
+
data={ordersData}
|
|
299
|
+
enableExpanding={true}
|
|
300
|
+
renderExpandedRow={(row) => <OrderDetailPanel orderId={row.original.id} />}
|
|
301
|
+
/>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### 6. Dữ liệu cây phân cấp đệ quy đa tầng (Multi-level Tree Data với `getSubRows`)
|
|
309
|
+
|
|
310
|
+
Khi các dòng con có **cùng kiểu dữ liệu và hiển thị CÙNG HÀNG & CÙNG CỘT** với bảng cha (hỗ trợ nhiều cấp cha ➔ con ➔ cháu...), chỉ cần cung cấp hàm `getSubRows`. Hệ thống tự động render dòng con với đầy đủ các cột và tự động thụt lề theo `row.depth`:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { DataTable, createTableColumnHelper } from "@openway/ui";
|
|
314
|
+
|
|
315
|
+
interface Department {
|
|
316
|
+
id: string;
|
|
317
|
+
name: string;
|
|
318
|
+
leader: string;
|
|
319
|
+
budget: number;
|
|
320
|
+
subRows?: Department[]; // Danh sách phòng ban con
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const deptColumnHelper = createTableColumnHelper<Department>();
|
|
324
|
+
|
|
325
|
+
const deptColumns = deptColumnHelper.columns([
|
|
326
|
+
deptColumnHelper.accessor("name", {
|
|
327
|
+
header: "Tên đơn vị",
|
|
328
|
+
}),
|
|
329
|
+
deptColumnHelper.accessor("leader", {
|
|
330
|
+
header: "Trưởng đơn vị",
|
|
331
|
+
}),
|
|
332
|
+
deptColumnHelper.accessor("budget", {
|
|
333
|
+
header: "Ngân sách",
|
|
334
|
+
cell: (info) => `${info.getValue().toLocaleString("vi-VN")} đ`,
|
|
335
|
+
}),
|
|
336
|
+
]);
|
|
337
|
+
|
|
338
|
+
export function DepartmentTreeTableExample() {
|
|
339
|
+
return (
|
|
340
|
+
<DataTable
|
|
341
|
+
columns={deptColumns}
|
|
342
|
+
data={departmentsData}
|
|
343
|
+
enableExpanding={true}
|
|
344
|
+
getSubRows={(row) => row.subRows}
|
|
345
|
+
maxIndentDepth={4} // Giới hạn thụt dòng tối đa 4 cấp để không làm co hẹp layout
|
|
346
|
+
indentSize={1.25} // 1.25rem mỗi cấp
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
257
354
|
## ⚙️ Bảng thuộc tính (Props Table)
|
|
258
355
|
|
|
259
356
|
### `<DataTable />`
|
|
@@ -278,6 +375,21 @@ export function ServerSideTableExample() {
|
|
|
278
375
|
| `enableColumnOrdering` | `boolean` | `false` | Bật tính năng kéo thả thay đổi thứ tự các cột (Drag-and-Drop Column Ordering) |
|
|
279
376
|
| `columnOrder` | `string[]` | `undefined` | Trạng thái mảng thứ tự các cột điều khiển ngoài |
|
|
280
377
|
| `onColumnOrderChange` | `(order) => void` | `undefined` | Callback khi thứ tự cột thay đổi do kéo thả |
|
|
378
|
+
| `enableExpanding` | `boolean` | `false` | Bật tính năng mở rộng dòng (tự động bật nếu có `renderExpandedRow` hoặc `getSubRows`) |
|
|
379
|
+
| `expanded` | `ExpandedState` | `undefined` | Trạng thái dòng mở rộng điều khiển ngoài (Controlled state) |
|
|
380
|
+
| `onExpandedChange` | `(expanded) => void` | `undefined` | Callback khi trạng thái mở rộng dòng thay đổi |
|
|
381
|
+
| `getSubRows` | `(row, index) => TData[] \| undefined` | `undefined` | Hàm lấy dữ liệu con cho cấu trúc cây đa tầng |
|
|
382
|
+
| `getRowCanExpand` | `(row) => boolean` | `undefined` | Điều kiện tùy biến xác định dòng nào có thể mở rộng |
|
|
383
|
+
| `renderExpandedRow` | `(row) => ReactNode` | `undefined` | Render component tùy biến (Detail Panel) bên dưới dòng cha khi mở rộng |
|
|
384
|
+
| `showExpandColumn` | `boolean` | `true` | Tự động hiển thị nút mở rộng dòng khi bật expanding |
|
|
385
|
+
| `expandColumnMode` | `'integrated' \| 'standalone' \| 'none'` | `'integrated'` | Chế độ hiển thị nút mở rộng (`integrated` gộp vào cột đầu tiên, `standalone` tách thành cột `_expand` riêng) |
|
|
386
|
+
| `expandColumnId` | `string` | `undefined` | ID của cột được gộp nút mở rộng khi dùng `expandColumnMode='integrated'` (mặc định lấy cột nội dung đầu tiên) |
|
|
387
|
+
| `expandColumnPosition` | `'start' \| 'end'` | `'start'` | Vị trí đặt cột nút mở rộng dòng khi ở chế độ `standalone` |
|
|
388
|
+
| `maxIndentDepth` | `number` | `4` | Cấp độ sâu thụt lề tối đa cho Tree Data (tránh làm vỡ layout khi lồng sâu) |
|
|
389
|
+
| `indentSize` | `number` | `1.25` | Kích thước thụt lề mỗi cấp tính theo đơn vị `rem` |
|
|
390
|
+
| `manualExpanding` | `boolean` | `false` | Bật chế độ mở rộng thủ công từ máy chủ (Server-side) |
|
|
391
|
+
| `autoResetExpanded` | `boolean` | `true` | Tự động thu gọn các dòng khi dữ liệu thay đổi |
|
|
392
|
+
| `paginateExpandedRows` | `boolean` | `true` | Phân trang các dòng con cùng với các dòng chính của bảng |
|
|
281
393
|
| `searchPlaceholder`| `string` | `"Tìm kiếm trong bảng..."` | Placeholder cho ô tìm kiếm |
|
|
282
394
|
| `toolbarActions` | `ReactNode` | `undefined` | Các nút hành động thêm ở góc phải toolbar |
|
|
283
395
|
| `renderBulkActions`| `(selectedRows) => ReactNode` | `undefined` | Render các nút hành động hàng loạt khi chọn dòng |
|
|
@@ -286,6 +398,7 @@ export function ServerSideTableExample() {
|
|
|
286
398
|
| `manualPagination` | `boolean` | `false` | Bật chế độ phân trang từ máy chủ (Server-side) |
|
|
287
399
|
| `manualSorting` | `boolean` | `false` | Bật chế độ sắp xếp từ máy chủ (Server-side) |
|
|
288
400
|
| `manualFiltering` | `boolean` | `false` | Bật chế độ tìm kiếm từ máy chủ (Server-side) |
|
|
401
|
+
| `debounceMs` | `number` | `300` | Thời gian trì hoãn debounce (ms) khi thay đổi bộ lọc hoặc tìm kiếm ở Server-side (`manualFiltering: true`). Đặt 0 để tắt debounce. |
|
|
289
402
|
| `pageCount` | `number` | `undefined` | Tổng số trang (khi dùng `manualPagination`) |
|
|
290
403
|
| `rowCount` | `number` | `undefined` | Tổng số dòng dữ liệu thực tế (khi dùng `manualPagination`) |
|
|
291
404
|
| `onRowClick` | `(row) => void` | `undefined` | Sự kiện click chuột vào một dòng |
|
|
@@ -58,7 +58,7 @@ export function PostFeed() {
|
|
|
58
58
|
return (
|
|
59
59
|
<div className="max-w-md mx-auto space-y-3">
|
|
60
60
|
{posts.map((post, idx) => (
|
|
61
|
-
<div key={idx} className="p-4 border rounded-lg shadow-sm bg-white
|
|
61
|
+
<div key={idx} className="p-4 border rounded-lg shadow-sm bg-white">
|
|
62
62
|
{post}
|
|
63
63
|
</div>
|
|
64
64
|
))}
|