Files
oranguru/src/Griddy/EXAMPLES.md
Hein ad325d94a9 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
2026-02-14 14:51:53 +02:00

11 KiB

Griddy Examples

Table of Contents

  1. Basic Grid
  2. Editable Grid
  3. Searchable Grid
  4. Filtered Grid
  5. Paginated Grid
  6. Server-Side Grid
  7. Custom Renderers
  8. Selection
  9. TypeScript Integration

Basic Grid

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

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

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

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

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

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

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

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

// 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}
    />
  )
}