@openway/ui 1.0.2 → 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.
@@ -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 dark:text-neutral-100">
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 dark:text-neutral-500">
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 dark:bg-purple-900/40"
163
- fillerClassName="bg-purple-600 dark:bg-purple-500"
162
+ trackClassName="bg-purple-200"
163
+ fillerClassName="bg-purple-600"
164
164
  thumbClassName="border-purple-600"
165
165
  />
166
166
  ```
@@ -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 dark:bg-neutral-900">
61
+ <div key={idx} className="p-4 border rounded-lg shadow-sm bg-white">
62
62
  {post}
63
63
  </div>
64
64
  ))}