Files
oranguru/src/Griddy/README.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

7.1 KiB

Griddy

A powerful, keyboard-first data grid component built on TanStack Table and TanStack Virtual with full TypeScript support.

Features

Core Features

  • 🎹 Keyboard-first navigation - Arrow keys, Page Up/Down, Home/End, Ctrl+F
  • 🚀 Virtual scrolling - Handle 10,000+ rows smoothly
  • 📝 Inline editing - 5 built-in editors (text, number, date, select, checkbox)
  • 🔍 Search - Ctrl+F overlay with highlighting
  • 🎯 Row selection - Single and multi-select modes with keyboard support
  • 📊 Sorting - Single and multi-column sorting
  • 🔎 Filtering - Text, number, date, enum, boolean filters with operators
  • 📄 Pagination - Client-side and server-side pagination
  • 💾 CSV Export - Export filtered data to CSV
  • 👁️ Column visibility - Show/hide columns dynamically

🎨 Advanced Features

  • Server-side filtering/sorting/pagination
  • Customizable cell renderers
  • Custom editors
  • Theme system with CSS variables
  • Fully accessible (ARIA compliant)

Installation

pnpm add @warkypublic/oranguru @tanstack/react-table @tanstack/react-virtual @mantine/core @mantine/dates

Quick Start

import { Griddy } from '@warkypublic/oranguru'
import type { GriddyColumn } from '@warkypublic/oranguru'

interface Person {
  id: number
  name: string
  age: number
  email: string
}

const columns: GriddyColumn<Person>[] = [
  { id: 'id', accessor: 'id', header: 'ID', width: 60 },
  { id: 'name', accessor: 'name', header: 'Name', width: 150, sortable: true },
  { id: 'age', accessor: 'age', header: 'Age', width: 80, sortable: true },
  { id: 'email', accessor: 'email', header: 'Email', width: 250 },
]

const data: Person[] = [
  { id: 1, name: 'Alice', age: 28, email: 'alice@example.com' },
  { id: 2, name: 'Bob', age: 32, email: 'bob@example.com' },
]

function MyGrid() {
  return (
    <Griddy
      columns={columns}
      data={data}
      height={400}
      getRowId={(row) => String(row.id)}
    />
  )
}

API Reference

GriddyProps

Prop Type Default Description
columns GriddyColumn<T>[] required Column definitions
data T[] required Data array
height number | string '100%' Container height
getRowId (row: T, index: number) => string (_, i) => String(i) Row ID function
rowHeight number 36 Row height in pixels
overscan number 10 Overscan row count
keyboardNavigation boolean true Enable keyboard shortcuts
selection SelectionConfig - Row selection config
search SearchConfig - Search config
pagination PaginationConfig - Pagination config
showToolbar boolean false Show toolbar (export + column visibility)
exportFilename string 'export.csv' CSV export filename
manualSorting boolean false Server-side sorting
manualFiltering boolean false Server-side filtering
dataCount number - Total row count (for server-side pagination)

Column Definition

interface GriddyColumn<T> {
  id: string
  accessor: keyof T | ((row: T) => any)
  header: string | ReactNode
  width?: number
  minWidth?: number
  maxWidth?: number
  sortable?: boolean
  filterable?: boolean
  filterConfig?: FilterConfig
  editable?: boolean
  editorConfig?: EditorConfig
  renderer?: CellRenderer<T>
  hidden?: boolean
  pinned?: 'left' | 'right'
}

Keyboard Shortcuts

Key Action
Arrow Up/Down Move focus between rows
Page Up/Down Jump by visible page size
Home / End Jump to first/last row
Space Toggle row selection
Shift + Arrow Extend selection (multi-select)
Ctrl + A Select all rows
Ctrl + F Open search overlay
Ctrl + E / Enter Start editing
Escape Cancel edit / close search / clear selection

Examples

With Editing

const editableColumns: GriddyColumn<Person>[] = [
  {
    id: 'name',
    accessor: 'name',
    header: 'Name',
    editable: true,
    editorConfig: { type: 'text' },
  },
  {
    id: 'age',
    accessor: 'age',
    header: 'Age',
    editable: true,
    editorConfig: { type: 'number', min: 0, max: 120 },
  },
]

<Griddy
  columns={editableColumns}
  data={data}
  onEditCommit={(rowId, columnId, value) => {
    // Update your data
    setData(prev => prev.map(row =>
      row.id === rowId ? { ...row, [columnId]: value } : row
    ))
  }}
/>

With Filtering

const filterableColumns: GriddyColumn<Person>[] = [
  {
    id: 'name',
    accessor: 'name',
    header: 'Name',
    filterable: true,
    filterConfig: { type: 'text' },
  },
  {
    id: 'age',
    accessor: 'age',
    header: 'Age',
    filterable: true,
    filterConfig: { type: 'number' },
  },
]

<Griddy
  columns={filterableColumns}
  data={data}
  columnFilters={filters}
  onColumnFiltersChange={setFilters}
/>

With Pagination

<Griddy
  columns={columns}
  data={data}
  pagination={{
    enabled: true,
    pageSize: 25,
    pageSizeOptions: [10, 25, 50, 100],
  }}
/>

Server-Side Mode

const [serverData, setServerData] = useState([])
const [filters, setFilters] = useState([])
const [sorting, setSorting] = useState([])

useEffect(() => {
  // Fetch from server when filters/sorting change
  fetchData({ filters, sorting }).then(setServerData)
}, [filters, sorting])

<Griddy
  columns={columns}
  data={serverData}
  manualFiltering
  manualSorting
  columnFilters={filters}
  onColumnFiltersChange={setFilters}
  sorting={sorting}
  onSortingChange={setSorting}
/>

Theming

Griddy uses CSS variables for theming:

.griddy {
  --griddy-font-family: inherit;
  --griddy-font-size: 14px;
  --griddy-border-color: #e0e0e0;
  --griddy-header-bg: #f8f9fa;
  --griddy-header-color: #212529;
  --griddy-row-bg: #ffffff;
  --griddy-row-hover-bg: #f1f3f5;
  --griddy-row-even-bg: #f8f9fa;
  --griddy-focus-color: #228be6;
  --griddy-selection-bg: rgba(34, 139, 230, 0.1);
}

Override in your CSS:

.my-custom-grid {
  --griddy-focus-color: #ff6b6b;
  --griddy-header-bg: #1a1b1e;
  --griddy-header-color: #ffffff;
}

Performance

  • Handles 10,000+ rows with virtual scrolling
  • 60 fps scrolling performance
  • Optimized with React.memo and useMemo
  • Only visible rows rendered (TanStack Virtual)
  • Bundle size: ~45KB gzipped (excluding peer deps)

Accessibility

Griddy follows WAI-ARIA grid pattern:

  • Full keyboard navigation
  • ARIA roles: grid, row, gridcell, columnheader
  • aria-selected on selected rows
  • aria-activedescendant for focused row
  • Screen reader compatible
  • Focus indicators

Browser Support

  • Chrome/Edge: Latest 2 versions
  • Firefox: Latest 2 versions
  • Safari: Latest 2 versions

License

MIT

Credits

Built with: