@sukritt/datagrid
v1.2.2
Published
React DataGrid component พร้อม virtual scroll, filter, group-by, column resize/reorder และ export CSV/Excel
Maintainers
Readme
@sukritt/datagrid
🇬🇧 English · 🇹🇭 ไทย
Custom zero-dependency DataGrid component for React with virtual scroll, cell editing, filtering, sorting, grouping, and export support.
Features
- Virtual scroll สำหรับข้อมูลจำนวนมาก
- Pagination
- Sort (คลิก header — Shift+คลิกเพื่อ multi-column sort), Filter (text / number / date / select dropdown)
- Global search + ปุ่มล้าง filter ทั้งหมด
- Keyboard navigation — ลูกศร / Home / End / PageUp / PageDown / Enter แก้ไข / Space เลือกแถว
- Column Group Headers — จัดกลุ่ม columns ใต้ parent header (multi-level)
- Column resize & drag-and-drop reorder (group width อัปเดตอัตโนมัติ)
- Freeze column ได้ทั้ง ซ้ายและขวา (คลิกขวาที่ header หรือ cell)
- Flex column sizing (
flexแบ่งพื้นที่ว่างตามสัดส่วน) - Cell editing — editor 4 แบบ (
text/number/date/select) + custom editor - Row selection (single / multiple พร้อม checkbox + select-all)
- Group by (drag header ไปที่แถบ group)
- Column visibility toggle
- Aggregation (sum / avg / count / min / max) + แถวสรุปรวม (grand total) ตรึงท้ายตาราง
- Master-detail — แผงรายละเอียดใต้แถวผ่าน
renderDetail - Clipboard — Ctrl+C คัดลอก cell, Ctrl+V วางหลาย cell จาก Excel/Sheets
- Column virtualization — เปิดอัตโนมัติเมื่อคอลัมน์เกิน 30 (render เฉพาะที่มองเห็น)
- Custom filter ต่อ column ผ่าน
filterFn - Export CSV & Excel
- Server-side mode — sort / filter / pagination ที่ backend
- CSS variable theming — ปรับ theme ได้ผ่าน
--dg-*custom properties + dark theme สำเร็จรูป (dg-theme-dark) - Density toggle — compact / normal / comfortable สลับได้จาก toolbar
- i18n — UI ไทย (default) / อังกฤษ ผ่าน prop
locale+ override รายข้อความด้วยlocaleText - TypeScript ready
Installation
npm install @sukritt/datagridUsage
import { DataGrid } from '@sukritt/datagrid'
import '@sukritt/datagrid/styles'
import type { ColumnDef, DataGridRef } from '@sukritt/datagrid'
interface Product {
id: number
name: string
price: number
}
const columns: ColumnDef<Product>[] = [
{ field: 'id', headerName: 'รหัส', width: 80 },
{ field: 'name', headerName: 'สินค้า', flex: 1 },
{ field: 'price', headerName: 'ราคา', width: 120, align: 'right', filter: 'number' },
]
export default function App() {
return (
<DataGrid<Product>
rowData={data}
columnDefs={columns}
height={400}
enableFilter
enableCellEdit
rowSelection="multiple"
pagination={{ enabled: true, pageSize: 20 }}
/>
)
}Export with ref
import { useRef } from 'react'
import { DataGrid } from '@sukritt/datagrid'
import type { DataGridRef } from '@sukritt/datagrid'
const gridRef = useRef<DataGridRef>(null)
<DataGrid ref={gridRef} rowData={data} columnDefs={columns} />
<button onClick={() => gridRef.current?.exportToCsv('report')}>Export CSV</button>
<button onClick={() => gridRef.current?.exportToExcel('report')}>Export Excel</button>Column Group Headers
จัดกลุ่ม columns ใต้ parent header เดียวกัน โดยใช้ ColumnGroupDef ใน columnDefs
หลักการ: columns ที่ต้องการให้อยู่ใต้ group เดียวกัน ให้ใส่ไว้ใน children ของ ColumnGroupDef — columns ที่ไม่อยู่ใน children จะแสดงเป็น column ปกติ
columnDefs = [
ColumnDef <- column ปกติ (ไม่มี group header)
ColumnDef <- column ปกติ
ColumnGroupDef <- group header "AMOUNT"
+-- ColumnDef <- อยู่ใต้ "AMOUNT"
+-- ColumnDef <- อยู่ใต้ "AMOUNT"
+-- ColumnDef <- อยู่ใต้ "AMOUNT"
]import type { ColumnDefOrGroup } from '@sukritt/datagrid'
interface SalesRow {
customer: string
region: string
amount2025: number
amount2026: number
lifetime: number
}
const columns: ColumnDefOrGroup<SalesRow>[] = [
// columns ปกติ -- ไม่อยู่ใน group ใดๆ
{ field: 'customer', headerName: 'ลูกค้า', width: 200 },
{ field: 'region', headerName: 'ภูมิภาค', width: 120 },
// ColumnGroupDef -- columns ใน children จะอยู่ใต้ "AMOUNT"
{
groupId: 'amount',
headerName: 'AMOUNT',
headerAlign: 'center',
children: [
{ field: 'amount2025', headerName: 'ยอดรวมในปี 2025', width: 160, align: 'right' },
{ field: 'amount2026', headerName: 'ยอดรวมในปี 2026', width: 160, align: 'right' },
{ field: 'lifetime', headerName: 'ตลอดอายุการใช้งาน', width: 180, align: 'right' },
],
},
]
export default function App() {
return <DataGrid rowData={data} columnDefs={columns} height={500} />
}หมายเหตุ: ชื่อ group header และ column ที่อยู่ใน group สามารถจัดการได้ผ่าน Sidebar (ปุ่ม columns มุมขวาบน) โดยไม่ต้องแก้ code
จัดการ Column Groups ผ่าน Sidebar
กดปุ่ม columns มุมขวาบนของ grid เพื่อเปิด Sidebar แล้วไปที่ section Column Groups
| การกระทำ | วิธี | | --- | --- | | สร้าง group ใหม่ | กด + พิมพ์ชื่อ กด Enter หรือปุ่ม + สีน้ำเงิน | | เพิ่ม column | ลาก column จาก list ด้านบนมาวางใน drop zone ของ group | | เอา column ออก | กด x ข้างชื่อ column ใน group | | แก้ชื่อ group | คลิกที่ชื่อ group แล้วพิมพ์ชื่อใหม่ได้เลย | | ลบ group | กด x ข้าง group header (columns กลับเป็นปกติ) |
ColumnGroupDef Options
| Option | Type | Description |
| --- | --- | --- |
| groupId | string | ID ของกลุ่ม ต้อง unique (required) |
| headerName | string | ชื่อที่แสดงบน group header (required) |
| children | ColumnDef<TData>[] | columns ที่จะอยู่ใต้ group นี้ (required) |
| headerAlign | 'left' \| 'center' \| 'right' | จัดตำแหน่ง label (default: center) |
| headerStyle | CSSProperties | custom style ของ group header cell |
columnDefsรองรับColumnDefและColumnGroupDefปนกันได้- Group header width คำนวณจาก child columns อัตโนมัติ และอัปเดตเมื่อ resize หรือซ่อน column
- Grid ที่ไม่มี group จะแสดง header row เดียวเหมือนเดิม (backward compatible)
Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| rowData | TData[] | required | ข้อมูลที่แสดงใน grid |
| columnDefs | ColumnDefOrGroup<TData>[] | required | นิยาม columns รองรับ ColumnGroupDef ได้ |
| height | number \| string | 500 | ความสูงของ grid |
| rowHeight | number | 40 | ความสูงต่อ row (px) |
| headerHeight | number | 40 | ความสูง header (px) |
| density | 'compact' \| 'normal' \| 'comfortable' | normal | ความหนาแน่นเริ่มต้น (สลับได้จากปุ่มใน toolbar) |
| locale | 'th' \| 'en' | th | ภาษาของ UI ทั้งหมด |
| localeText | Partial<DataGridLocale> | - | override ข้อความ UI รายตัว |
| pagination | PaginationOptions | enabled, 20/page | ตั้งค่า pagination |
| enableFilter | boolean | true | เปิด filter row |
| enableColumnResize | boolean | true | ลาก resize column |
| enableColumnReorder | boolean | true | ลาก reorder column |
| enableCellEdit | boolean | false | ดับเบิลคลิกแก้ไข cell |
| rowSelection | 'single' \| 'multiple' \| false | false | รูปแบบการเลือก row |
| showTotalsRow | boolean | true | แถวสรุปรวมตรึงท้ายตาราง (แสดงเมื่อตั้ง Values ใน sidebar) |
| renderDetail | (params) => ReactNode | - | แผงรายละเอียดใต้แถว (master-detail) |
| detailRowHeight | number | 240 | ความสูงแผงรายละเอียด (px, คงที่) |
| loading | boolean | false | แสดง loading overlay |
| noRowsText | string | ไม่มีข้อมูล | ข้อความเมื่อไม่มีข้อมูล |
| onRowClick | (data, rowIndex) => void | - | callback เมื่อคลิก row |
| onCellValueChanged | (params) => void | - | callback เมื่อแก้ไข cell |
| onSelectionChanged | (selectedRows) => void | - | callback เมื่อ selection เปลี่ยน |
| serverSide | boolean | false | เปิด server-side mode (sort/filter/pagination ที่ backend) |
| totalRows | number | - | จำนวนแถวทั้งหมดจาก server (ใช้เมื่อ serverSide) |
| onServerRequest | (params: ServerRequestParams) => void | - | ยิงเมื่อ page/sort/filter เปลี่ยนใน server mode |
| style | CSSProperties | - | inline style — ใช้ override CSS variables ได้ ({ '--dg-primary': '#f00' }) |
Server-side mode
เมื่อ serverSide เป็น true grid จะไม่ประมวลผล rowData เอง แต่จะยิง onServerRequest เมื่อ page / sort / filter เปลี่ยน ให้ consumer ไป fetch แล้วป้อน rowData (เฉพาะหน้าปัจจุบัน) + totalRows + loading กลับมา (controlled pattern)
const [rows, setRows] = useState<Row[]>([])
const [total, setTotal] = useState(0)
const [loading, setLoading] = useState(false)
const handleRequest = (p: ServerRequestParams) => {
setLoading(true)
fetch(`/api/rows?page=${p.page}&size=${p.pageSize}` +
`&sort=${p.sortModel?.field ?? ''}:${p.sortModel?.direction ?? ''}` +
`&q=${p.filterModel.globalSearch}`)
.then(r => r.json())
.then(({ rows, total }) => { setRows(rows); setTotal(total); setLoading(false) })
}
<DataGrid
serverSide
rowData={rows}
totalRows={total}
loading={loading}
onServerRequest={handleRequest}
columnDefs={cols}
/>ServerRequestParams = { page (1-based), pageSize, sortModel: SortModel | null, sortModels: SortModel[], filterModel: FilterModel } — sortModels คือ sort ทั้งชุดเรียงตามลำดับความสำคัญ (multi-sort ผ่าน Shift+คลิก); sortModel คือตัวแรก คงไว้เพื่อ backward compatibility. filter/search ถูก debounce 300ms; เมื่อ sort/filter เปลี่ยน grid จะรีเซ็ตไปหน้า 1 ให้อัตโนมัติและยิง request ครั้งเดียว
หมายเหตุ: grouping ไม่รองรับใน server mode (เฟสนี้); select-filter options มาจากหน้าปัจจุบันเท่านั้น
Theming (CSS variables)
Grid ใช้ CSS custom properties (--dg-*) ที่ประกาศใน :root ทั้งหมดมีค่า default — override ได้ทุกระดับ:
/* override ทั้งแอป */
:root { --dg-primary: #e11d48; --dg-row-selected: #fce7f3; }/* override เฉพาะ instance ผ่าน style prop */
<DataGrid style={{ '--dg-primary': '#e11d48' } as CSSProperties} ... />Token หลัก: --dg-primary, --dg-primary-dark, --dg-primary-light, --dg-surface, --dg-border-color, --dg-text, --dg-text-muted, --dg-text-secondary, --dg-danger, --dg-header-bg, --dg-header-hover, --dg-row-bg, --dg-row-alt, --dg-row-hover, --dg-row-selected, --dg-btn-border, --dg-icon-muted, --dg-on-primary, --dg-editable-hover, --dg-overlay-bg, --dg-font, --dg-font-size(-sm/-xs), --dg-cell-padding, --dg-radius(-sm), --dg-shadow(-md/-lg), --dg-transition(-fast)
Dark theme สำเร็จรูป
ใส่ class dg-theme-dark ที่ <html> หรือ <body> (แนะนำ — เมนู/dropdown ที่ render ผ่าน portal จะได้ theme ด้วย):
document.documentElement.classList.toggle('dg-theme-dark', isDark)ภาษา (i18n)
<DataGrid locale="en" /> {/* UI อังกฤษทั้งหมด */}
<DataGrid locale="th" localeText={{ noRows: 'ว่างเปล่า' }} /> {/* override รายข้อความ */}LOCALE_TH / LOCALE_EN และ type DataGridLocale ถูก export จาก package สำหรับสร้าง locale ของตัวเอง
ColumnDef
| Option | Type | Description |
| --- | --- | --- |
| field | string | key ใน rowData (required) |
| headerName | string | ชื่อ header |
| width | number | ความกว้างคงที่ (px) |
| flex | number | แบ่งพื้นที่ว่างที่เหลือตามสัดส่วน (เคารพ minWidth/maxWidth; ผู้ใช้ลาก resize แล้วจะใช้ค่าที่ลาก) |
| align | 'left' \| 'center' \| 'right' | จัดตำแหน่ง |
| filter | boolean \| 'text' \| 'number' \| 'date' \| 'select' | ประเภท filter |
| filterFn | (cellValue, filterValue, data) => boolean | custom filter — แทน logic built-in ของ column นี้ |
| sortable | boolean | เรียงลำดับได้ |
| resizable | boolean | ลาก resize ได้ |
| editable | boolean | แก้ไขได้ |
| cellEditor | 'text' \| 'number' \| 'date' \| 'select' \| (params) => ReactNode | ชนิด editor (default text) — number commit เป็นตัวเลข, select เป็น dropdown, function = custom editor |
| cellEditorOptions | string[] \| (params) => string[] | ตัวเลือกของ select editor (ไม่กำหนด = unique values จากข้อมูล) |
| hide | boolean | ซ่อน column |
| cellRenderer | (params) => ReactNode | custom cell renderer |
| valueFormatter | (params) => string | format ค่าก่อนแสดง |
| cellStyle | CSSProperties \| (params) => CSSProperties | style ของ cell |
DataGridRef Methods
| Method | Description |
| --- | --- |
| exportToCsv(fileName?) | ดาวน์โหลด CSV |
| exportToExcel(fileName?) | ดาวน์โหลด Excel (.xlsx) |
| getSelectedRows() | ดึง array ของ row ที่เลือก |
| selectAll() | เลือก row ทั้งหมด |
| deselectAll() | ยกเลิกการเลือกทั้งหมด |
| clearFilters() | ล้าง filter ทุก column + global search |
| autoSizeColumns() | auto-size ทุก column ตามเนื้อหา |
| autoSizeColumn(field) | auto-size column เดียว |
| getState() | snapshot layout ทั้งหมด (DataGridState — serialize เป็น JSON ได้) |
| setState(state) | restore layout จาก snapshot (ใช้เฉพาะ field ที่ส่งมา) |
State persistence (เซฟ layout ผู้ใช้)
getState() คืน snapshot ของทุกอย่างที่ผู้ใช้ปรับได้ — ลำดับ/ความกว้าง/ซ่อน/freeze column, sort, filter, หน้า, density, group by, aggregation และ column group overrides:
// เซฟ
localStorage.setItem('my-grid', JSON.stringify(gridRef.current!.getState()))
// คืนค่า (เช่นตอน mount)
const saved = localStorage.getItem('my-grid')
if (saved) gridRef.current!.setState(JSON.parse(saved))Peer Dependencies
{
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}License
MIT
