- Implemented GridToolbar component for column visibility and CSV export - Added ColumnVisibilityMenu for toggling column visibility - Created exportToCsv function for exporting visible data to CSV - Updated Griddy component to integrate toolbar functionality - Enhanced documentation with examples for new features
472 lines
11 KiB
Markdown
472 lines
11 KiB
Markdown
# Griddy Examples
|
|
|
|
## Table of Contents
|
|
|
|
1. [Basic Grid](#basic-grid)
|
|
2. [Editable Grid](#editable-grid)
|
|
3. [Searchable Grid](#searchable-grid)
|
|
4. [Filtered Grid](#filtered-grid)
|
|
5. [Paginated Grid](#paginated-grid)
|
|
6. [Server-Side Grid](#server-side-grid)
|
|
7. [Custom Renderers](#custom-renderers)
|
|
8. [Selection](#selection)
|
|
9. [TypeScript Integration](#typescript-integration)
|
|
|
|
## Basic Grid
|
|
|
|
```typescript
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
|
|
interface Product {
|
|
id: number
|
|
name: string
|
|
price: number
|
|
inStock: boolean
|
|
}
|
|
|
|
const columns: GriddyColumn<Product>[] = [
|
|
{ id: 'id', accessor: 'id', header: 'ID', width: 60 },
|
|
{ id: 'name', accessor: 'name', header: 'Product Name', width: 200, sortable: true },
|
|
{ id: 'price', accessor: 'price', header: 'Price', width: 100, sortable: true },
|
|
{ id: 'inStock', accessor: row => row.inStock ? 'Yes' : 'No', header: 'In Stock', width: 100 },
|
|
]
|
|
|
|
const data: Product[] = [
|
|
{ id: 1, name: 'Laptop', price: 999, inStock: true },
|
|
{ id: 2, name: 'Mouse', price: 29, inStock: false },
|
|
]
|
|
|
|
export function ProductGrid() {
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={data}
|
|
height={500}
|
|
getRowId={(row) => String(row.id)}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Editable Grid
|
|
|
|
```typescript
|
|
import { useState } from 'react'
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
|
|
interface User {
|
|
id: number
|
|
firstName: string
|
|
lastName: string
|
|
age: number
|
|
role: string
|
|
}
|
|
|
|
export function EditableUserGrid() {
|
|
const [users, setUsers] = useState<User[]>([
|
|
{ id: 1, firstName: 'John', lastName: 'Doe', age: 30, role: 'Admin' },
|
|
{ id: 2, firstName: 'Jane', lastName: 'Smith', age: 25, role: 'User' },
|
|
])
|
|
|
|
const columns: GriddyColumn<User>[] = [
|
|
{ id: 'id', accessor: 'id', header: 'ID', width: 60 },
|
|
{
|
|
id: 'firstName',
|
|
accessor: 'firstName',
|
|
header: 'First Name',
|
|
width: 150,
|
|
editable: true,
|
|
editorConfig: { type: 'text' },
|
|
},
|
|
{
|
|
id: 'lastName',
|
|
accessor: 'lastName',
|
|
header: 'Last Name',
|
|
width: 150,
|
|
editable: true,
|
|
editorConfig: { type: 'text' },
|
|
},
|
|
{
|
|
id: 'age',
|
|
accessor: 'age',
|
|
header: 'Age',
|
|
width: 80,
|
|
editable: true,
|
|
editorConfig: { type: 'number', min: 18, max: 120 },
|
|
},
|
|
{
|
|
id: 'role',
|
|
accessor: 'role',
|
|
header: 'Role',
|
|
width: 120,
|
|
editable: true,
|
|
editorConfig: {
|
|
type: 'select',
|
|
options: [
|
|
{ label: 'Admin', value: 'Admin' },
|
|
{ label: 'User', value: 'User' },
|
|
{ label: 'Guest', value: 'Guest' },
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
const handleEditCommit = async (rowId: string, columnId: string, value: unknown) => {
|
|
setUsers(prev => prev.map(user =>
|
|
String(user.id) === rowId
|
|
? { ...user, [columnId]: value }
|
|
: user
|
|
))
|
|
}
|
|
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={users}
|
|
height={500}
|
|
getRowId={(row) => String(row.id)}
|
|
onEditCommit={handleEditCommit}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Searchable Grid
|
|
|
|
```typescript
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
|
|
export function SearchableGrid() {
|
|
const columns: GriddyColumn<Person>[] = [
|
|
{ id: 'name', accessor: 'name', header: 'Name', width: 150, searchable: true },
|
|
{ id: 'email', accessor: 'email', header: 'Email', width: 250, searchable: true },
|
|
{ id: 'department', accessor: 'department', header: 'Department', width: 150 },
|
|
]
|
|
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={data}
|
|
height={500}
|
|
search={{
|
|
enabled: true,
|
|
highlightMatches: true,
|
|
placeholder: 'Search by name or email...',
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Filtered Grid
|
|
|
|
```typescript
|
|
import { useState } from 'react'
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
import type { ColumnFiltersState } from '@tanstack/react-table'
|
|
|
|
export function FilteredGrid() {
|
|
const [filters, setFilters] = useState<ColumnFiltersState>([])
|
|
|
|
const columns: GriddyColumn<Person>[] = [
|
|
{
|
|
id: 'name',
|
|
accessor: 'name',
|
|
header: 'Name',
|
|
filterable: true,
|
|
filterConfig: { type: 'text' },
|
|
width: 150,
|
|
},
|
|
{
|
|
id: 'age',
|
|
accessor: 'age',
|
|
header: 'Age',
|
|
filterable: true,
|
|
filterConfig: { type: 'number' },
|
|
width: 80,
|
|
},
|
|
{
|
|
id: 'department',
|
|
accessor: 'department',
|
|
header: 'Department',
|
|
filterable: true,
|
|
filterConfig: {
|
|
type: 'enum',
|
|
enumOptions: [
|
|
{ label: 'Engineering', value: 'Engineering' },
|
|
{ label: 'Marketing', value: 'Marketing' },
|
|
{ label: 'Sales', value: 'Sales' },
|
|
],
|
|
},
|
|
width: 150,
|
|
},
|
|
]
|
|
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={data}
|
|
height={500}
|
|
columnFilters={filters}
|
|
onColumnFiltersChange={setFilters}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Paginated Grid
|
|
|
|
```typescript
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
|
|
export function PaginatedGrid() {
|
|
const columns: GriddyColumn<Person>[] = [
|
|
{ id: 'id', accessor: 'id', header: 'ID', width: 60 },
|
|
{ id: 'name', accessor: 'name', header: 'Name', width: 150 },
|
|
{ id: 'email', accessor: 'email', header: 'Email', width: 250 },
|
|
]
|
|
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={largeDataset}
|
|
height={500}
|
|
pagination={{
|
|
enabled: true,
|
|
pageSize: 25,
|
|
pageSizeOptions: [10, 25, 50, 100],
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Server-Side Grid
|
|
|
|
```typescript
|
|
import { useState, useEffect } from 'react'
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
import type { ColumnFiltersState, SortingState } from '@tanstack/react-table'
|
|
|
|
export function ServerSideGrid() {
|
|
const [data, setData] = useState([])
|
|
const [totalCount, setTotalCount] = useState(0)
|
|
const [filters, setFilters] = useState<ColumnFiltersState>([])
|
|
const [sorting, setSorting] = useState<SortingState>([])
|
|
const [pageIndex, setPageIndex] = useState(0)
|
|
const [pageSize, setPageSize] = useState(25)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
// Fetch data when filters, sorting, or pagination changes
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
setIsLoading(true)
|
|
try {
|
|
const response = await fetch('/api/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
filters,
|
|
sorting,
|
|
pagination: { pageIndex, pageSize },
|
|
}),
|
|
})
|
|
const result = await response.json()
|
|
setData(result.data)
|
|
setTotalCount(result.total)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
fetchData()
|
|
}, [filters, sorting, pageIndex, pageSize])
|
|
|
|
const columns: GriddyColumn<Person>[] = [
|
|
{
|
|
id: 'name',
|
|
accessor: 'name',
|
|
header: 'Name',
|
|
sortable: true,
|
|
filterable: true,
|
|
filterConfig: { type: 'text' },
|
|
width: 150,
|
|
},
|
|
// ... more columns
|
|
]
|
|
|
|
return (
|
|
<Griddy
|
|
columns={columns}
|
|
data={data}
|
|
dataCount={totalCount}
|
|
height={500}
|
|
manualSorting
|
|
manualFiltering
|
|
columnFilters={filters}
|
|
onColumnFiltersChange={setFilters}
|
|
sorting={sorting}
|
|
onSortingChange={setSorting}
|
|
pagination={{
|
|
enabled: true,
|
|
pageSize,
|
|
pageSizeOptions: [10, 25, 50, 100],
|
|
onPageChange: setPageIndex,
|
|
onPageSizeChange: (size) => {
|
|
setPageSize(size)
|
|
setPageIndex(0)
|
|
},
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Custom Renderers
|
|
|
|
```typescript
|
|
import { Griddy, type GriddyColumn, type CellRenderer } from '@warkypublic/oranguru'
|
|
import { Badge } from '@mantine/core'
|
|
|
|
interface Order {
|
|
id: number
|
|
customer: string
|
|
amount: number
|
|
status: 'pending' | 'shipped' | 'delivered'
|
|
}
|
|
|
|
const StatusRenderer: CellRenderer<Order> = ({ value }) => {
|
|
const color = value === 'delivered' ? 'green' : value === 'shipped' ? 'blue' : 'yellow'
|
|
return <Badge color={color}>{String(value)}</Badge>
|
|
}
|
|
|
|
const AmountRenderer: CellRenderer<Order> = ({ value }) => {
|
|
const amount = Number(value)
|
|
const color = amount > 1000 ? 'green' : 'gray'
|
|
return <span style={{ color, fontWeight: 600 }}>${amount.toFixed(2)}</span>
|
|
}
|
|
|
|
export function OrderGrid() {
|
|
const columns: GriddyColumn<Order>[] = [
|
|
{ id: 'id', accessor: 'id', header: 'Order ID', width: 100 },
|
|
{ id: 'customer', accessor: 'customer', header: 'Customer', width: 200 },
|
|
{
|
|
id: 'amount',
|
|
accessor: 'amount',
|
|
header: 'Amount',
|
|
width: 120,
|
|
renderer: AmountRenderer,
|
|
},
|
|
{
|
|
id: 'status',
|
|
accessor: 'status',
|
|
header: 'Status',
|
|
width: 120,
|
|
renderer: StatusRenderer,
|
|
},
|
|
]
|
|
|
|
return <Griddy columns={columns} data={orders} height={500} />
|
|
}
|
|
```
|
|
|
|
## Selection
|
|
|
|
```typescript
|
|
import { useState } from 'react'
|
|
import { Griddy, type GriddyColumn } from '@warkypublic/oranguru'
|
|
import type { RowSelectionState } from '@tanstack/react-table'
|
|
|
|
export function SelectableGrid() {
|
|
const [selection, setSelection] = useState<RowSelectionState>({})
|
|
|
|
const columns: GriddyColumn<Person>[] = [
|
|
{ id: 'name', accessor: 'name', header: 'Name', width: 150 },
|
|
{ id: 'email', accessor: 'email', header: 'Email', width: 250 },
|
|
]
|
|
|
|
const selectedRows = Object.keys(selection).filter(key => selection[key])
|
|
|
|
return (
|
|
<>
|
|
<Griddy
|
|
columns={columns}
|
|
data={data}
|
|
height={500}
|
|
rowSelection={selection}
|
|
onRowSelectionChange={setSelection}
|
|
selection={{
|
|
mode: 'multi',
|
|
showCheckbox: true,
|
|
selectOnClick: true,
|
|
}}
|
|
/>
|
|
<div>Selected: {selectedRows.length} rows</div>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
## TypeScript Integration
|
|
|
|
```typescript
|
|
// Define your data type
|
|
interface Employee {
|
|
id: number
|
|
firstName: string
|
|
lastName: string
|
|
email: string
|
|
department: string
|
|
salary: number
|
|
hireDate: string
|
|
isActive: boolean
|
|
}
|
|
|
|
// Type-safe column definition
|
|
const columns: GriddyColumn<Employee>[] = [
|
|
{
|
|
id: 'id',
|
|
accessor: 'id', // Type-checked against Employee keys
|
|
header: 'ID',
|
|
width: 60,
|
|
},
|
|
{
|
|
id: 'fullName',
|
|
accessor: (row) => `${row.firstName} ${row.lastName}`, // Type-safe accessor function
|
|
header: 'Full Name',
|
|
width: 200,
|
|
},
|
|
{
|
|
id: 'salary',
|
|
accessor: 'salary',
|
|
header: 'Salary',
|
|
width: 120,
|
|
renderer: ({ value }) => `$${Number(value).toLocaleString()}`,
|
|
},
|
|
]
|
|
|
|
// Type-safe component
|
|
export function EmployeeGrid() {
|
|
const [employees, setEmployees] = useState<Employee[]>([])
|
|
|
|
const handleEdit = async (rowId: string, columnId: string, value: unknown) => {
|
|
// TypeScript knows employees is Employee[]
|
|
setEmployees(prev => prev.map(emp =>
|
|
String(emp.id) === rowId
|
|
? { ...emp, [columnId]: value }
|
|
: emp
|
|
))
|
|
}
|
|
|
|
return (
|
|
<Griddy<Employee>
|
|
columns={columns}
|
|
data={employees}
|
|
height={600}
|
|
getRowId={(row) => String(row.id)}
|
|
onEditCommit={handleEdit}
|
|
/>
|
|
)
|
|
}
|
|
```
|