Mobile Table Card Pattern
Vấn đề
Bảng dữ liệu (Table) trên desktop hoạt động tốt với nhiều cột. Trên mobile, bảng bị tràn ngang và khó tương tác. Cần một cách hiển thị linh hoạt:
- Desktop: bảng truyền thống (thead/tbody)
- Mobile: danh sách card, mỗi row là 1 card
Giải pháp — Kiến trúc Slotted & Escape Hatch
Hệ thống Table mới cung cấp cơ chế tự động đáp ứng (responsive) theo hai cấp độ:
Table (Public API)
├── Desktop Mode (md+) → <table> HTML chuẩn
└── Mobile Mode (<md) → TableMobileCard (Internal Logic)
├── Level 1: Auto-columns (Mặc định)
└── Level 2: Custom Render (Dùng renderMobileCard)
Luồng xử lý Logic
- Mặc định: Nếu không truyền gì thêm, Table tự động chuyển sang dạng danh sách các cột (Label: Value) khi ở màn hình nhỏ.
- Tùy biến: Nếu muốn card có UI đặc thù (như Đơn hàng), Developer cung cấp hàm
renderMobileCard.
Cách dùng chuẩn
1. Hiển thị mặc định (Auto Columns)
Áp dụng cho các bảng báo cáo hoặc danh sách đơn giản. Không cần cấu hình thêm.
import { Table } from "@vppos/core/ui/react";
<Table columns={columns} data={data} />;
// Desktop: <table>
// Mobile: Card list (tự động loop qua columns)
2. Hiển thị tùy biến (Custom Render)
Áp dụng cho các trang cần UI mobile đặc biệt (Đơn hàng, Giao dịch). Sử dụng renderMobileCard.
import { Table } from "@vppos/core/ui/react";
<Table
columns={columns}
data={orders}
renderMobileCard={(order) => (
<div className="rounded-xl border border-gray-100 bg-white p-4 shadow-sm">
<div className="flex items-start justify-between gap-3">
<h3 className="text-lg font-medium">{order.code}</h3>
<Badge>{order.status}</Badge>
</div>
<p className="mt-2 text-sm text-gray-500">{order.customerName}</p>
<div className="mt-4 flex items-center justify-between">
<span className="text-xl font-medium">{formatCurrency(order.total)}</span>
<Button onClick={() => handleApprove(order)}>Phê duyệt</Button>
</div>
</div>
)}
/>;
Tại sao kiến trúc này tối ưu?
- Zero-Config: Đa số các bảng sẽ hoạt động tốt trên mobile mà không cần code thêm một dòng nào.
- Toàn quyền kiểm soát (Full Control): Với
renderMobileCard, bạn có thể vẽ bất cứ thứ gì (Avatar, ProgressBar, Map...) mà không bị giới hạn bởi các "variant" cứng nhắc trong Core. - Dễ bảo trì: Logic hiển thị của module nào nằm ở module đó (Local Logic). Core chỉ đóng vai trò là "khung xương" điều phối.
Loading & Skeletion
Hệ thống tự động sử dụng Skeleton phù hợp:
- Nếu dùng mặc định: Skeleton dạng danh sách dòng.
- Nếu dùng
renderMobileCard: Developer có thể tự định nghĩa skeleton hoặc hệ thống sẽ dùng skeleton card tiêu chuẩn.
Files liên quan
| File | Vai trò |
|---|---|
core/src/ui/react/Table.tsx | Public API — điều phối Desktop/Mobile và nhận renderMobileCard. |
core/src/ui/react/TableMobileCard.tsx | Internal — thực hiện việc render item hoặc mặc định. |
apps/sale-manage/.../order-mobile-list.tsx | Ví dụ áp dụng thực tế (Best Practice). |