npm package: @sukritt/datagrid
Description: Custom zero-dependency DataGrid component for React
Latest version: 1.0.5

# @sukritt/datagrid

Custom zero-dependency DataGrid component for React with virtual scroll, cell editing, filtering, sorting, grouping, and export support.

## Features

- Virtual scroll สำหรับข้อมูลจำนวนมาก
- Pagination
- Sort, Filter (text / number / date / select dropdown)
- Global search
- **Column Group Headers** — จัดกลุ่ม columns ใต้ parent header (multi-level)
- Column resize & drag-and-drop reorder (group width อัปเดตอัตโนมัติ)
- Cell editing
- Row selection (single / multiple)
- Group by (drag header ไปที่แถบ group)
- Column visibility toggle
- Aggregation (sum / avg / count / min / max)
- Export CSV & Excel
- Zero runtime dependencies (ยกเว้น React)
- TypeScript ready

## Installation

```bash
npm install @sukritt/datagrid
```

## Usage

```tsx
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

```tsx
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 ปกติ

```text
columnDefs = [
  ColumnDef          <- column ปกติ (ไม่มี group header)
  ColumnDef          <- column ปกติ
  ColumnGroupDef     <- group header "AMOUNT"
    +-- ColumnDef    <- อยู่ใต้ "AMOUNT"
    +-- ColumnDef    <- อยู่ใต้ "AMOUNT"
    +-- ColumnDef    <- อยู่ใต้ "AMOUNT"
]
```

```tsx
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) |
| `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 |
| `loading` | `boolean` | false | แสดง loading overlay |
| `noRowsText` | `string` | ไม่มีข้อมูล | ข้อความเมื่อไม่มีข้อมูล |
| `onRowClick` | `(data, rowIndex) => void` | - | callback เมื่อคลิก row |
| `onCellValueChanged` | `(params) => void` | - | callback เมื่อแก้ไข cell |
| `onSelectionChanged` | `(selectedRows) => void` | - | callback เมื่อ selection เปลี่ยน |

## ColumnDef

| Option | Type | Description |
| --- | --- | --- |
| `field` | `string` | key ใน rowData (required) |
| `headerName` | `string` | ชื่อ header |
| `width` | `number` | ความกว้างคงที่ (px) |
| `flex` | `number` | stretch เต็มพื้นที่ |
| `align` | `'left' \| 'center' \| 'right'` | จัดตำแหน่ง |
| `filter` | `boolean \| 'text' \| 'number' \| 'date' \| 'select'` | ประเภท filter |
| `sortable` | `boolean` | เรียงลำดับได้ |
| `resizable` | `boolean` | ลาก resize ได้ |
| `editable` | `boolean` | แก้ไขได้ |
| `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()` | ยกเลิกการเลือกทั้งหมด |

## Peer Dependencies

```json
{
  "react": ">=18.0.0",
  "react-dom": ">=18.0.0"
}
```

## License

MIT