feat(toolbar): add column visibility and CSV export features
- 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
This commit is contained in:
471
src/Griddy/EXAMPLES.md
Normal file
471
src/Griddy/EXAMPLES.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user