npm package: @sproutsocial/seeds-react-data-table
Description: Seeds React DataTable
Author: Sprout Social, Inc.
Latest version: 2.6.5

# DataTable

A flexible, accessible data table component built with TanStack Table. Designed for displaying structured data with support for sorting, custom rendering, and both simple and advanced use cases.

## Installation

```bash
yarn add @sproutsocial/seeds-react-data-table
```

## Features

- **Simple String Columns**: Quick setup with automatic camelCase key mapping
- **TanStack Table Integration**: Full control with ColumnDef for advanced features
- **Sorting**: Built-in column sorting functionality
- **Search/Filtering**: Fuzzy search across all columns with TanStack Table
- **Pagination**: Simple show more/less pagination support
- **Custom Cell Rendering**: Flexible cell rendering for API-driven UIs
- **Custom Caption Rendering**: Support for complex table captions with badges and icons
- **Accessible**: Required captions for screen readers
- **TypeScript Support**: Full type safety with generics

## Basic Usage

### Simple String Columns

The easiest way to get started. Column names automatically map to data keys via camelCase conversion.

```tsx
import { DataTable } from '@sproutsocial/seeds-react-data-table';

const data = [
  { name: 'John Doe', age: 28, occupation: 'Engineer' },
  { name: 'Jane Smith', age: 32, occupation: 'Designer' },
];

<DataTable
  columns={['Name', 'Age', 'Occupation']}
  data={data}
  caption="Team Members"
/>
```

**Column Mapping Examples:**
- `"Name"` → `name`
- `"First Name"` → `firstName`
- `"Email Address"` → `emailAddress`

### Advanced TanStack ColumnDef

For full control over rendering, sorting, and behavior:

```tsx
import { DataTable } from '@sproutsocial/seeds-react-data-table';
import type { ColumnDef } from '@tanstack/react-table';

interface Person {
  name: string;
  email: string;
  active: boolean;
}

const columns: ColumnDef<Person>[] = [
  {
    accessorKey: 'name',
    header: 'Full Name',
    cell: (info) => <strong>{info.getValue()}</strong>,
  },
  {
    accessorKey: 'email',
    header: 'Email',
    cell: (info) => (
      <a href={`mailto:${info.getValue()}`}>{info.getValue()}</a>
    ),
  },
  {
    accessorKey: 'active',
    header: 'Status',
    cell: (info) => (
      <Badge>{info.getValue() ? 'Active' : 'Inactive'}</Badge>
    ),
  },
];

<DataTable
  columns={columns}
  data={data}
  caption="User Directory"
/>
```

## Custom Cell Rendering

Use `renderCell` to customize how all cells are rendered when using string columns. Perfect for API-driven UIs where you want to apply consistent rendering logic.

### Basic Custom Cell Rendering

```tsx
<DataTable
  columns={['Name', 'Email', 'Status']}
  data={data}
  caption="Users"
  renderCell={(info) => {
    const value = info.getValue();
    const columnId = info.column.id;

    if (columnId === 'email') {
      return <a href={`mailto:${value}`}>{value}</a>;
    }

    if (columnId === 'status') {
      return <Badge>{value ? 'Active' : 'Inactive'}</Badge>;
    }

    return value;
  }}
/>
```

### API-Driven Cell Rendering

Handle JSON responses with type information:

```tsx
// API returns:
const apiData = [
  {
    name: 'John Doe',
    email: { type: 'link', value: 'john@example.com', href: 'mailto:john@example.com' },
    status: { type: 'badge', value: 'Active', color: 'green' },
  },
];

<DataTable
  columns={['Name', 'Email', 'Status']}
  data={apiData}
  caption="Users"
  renderCell={(info) => {
    const value = info.getValue();

    // Handle objects with type hints
    if (typeof value === 'object' && value !== null && 'type' in value) {
      const cellData = value as any;

      if (cellData.type === 'link') {
        return <a href={cellData.href}>{cellData.value}</a>;
      }

      if (cellData.type === 'badge') {
        return <Badge badgeColor={cellData.color}>{cellData.value}</Badge>;
      }
    }

    return value;
  }}
/>
```

### Conditional Cell Styling

Apply styling based on values:

```tsx
<DataTable
  columns={['Name', 'Score']}
  data={data}
  caption="Performance Scores"
  renderCell={(info) => {
    const value = info.getValue();
    const columnId = info.column.id;

    if (columnId === 'score') {
      const score = value as number;
      const color = score >= 90 ? 'green' : score >= 80 ? 'orange' : 'red';

      return (
        <span style={{ color, fontWeight: 'bold' }}>
          {score}/100
        </span>
      );
    }

    return value;
  }}
/>
```

## Custom Caption Rendering

Use `renderCaption` to create complex table captions with badges, icons, and custom layouts.

### Simple Caption

```tsx
<DataTable
  columns={['Name', 'Age']}
  data={data}
  caption="Team Members"
/>
```

### Caption with Badges

```tsx
<DataTable
  columns={['Name', 'Age']}
  data={data}
  caption={{
    title: 'Team Members',
    badges: [
      { text: 'Q1 2024' },
      { text: 'Active' },
    ],
  }}
  renderCaption={(captionData: any) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
      <span>{captionData.title}</span>
      {captionData.badges?.map((badge: any, index: number) => (
        <Badge key={index}>{badge.text}</Badge>
      ))}
    </div>
  )}
/>
```

### Caption with Icons

```tsx
<DataTable
  columns={['Name', 'Age']}
  data={data}
  caption={{
    title: 'Post Performance',
    badges: [
      { text: 'Jan 1 - Jan 31', iconName: 'calendar-outline' },
      { text: 'Social Media', iconName: 'tag-outline' },
    ],
  }}
  renderCaption={(captionData: any) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
      <span>{captionData.title}</span>
      {captionData.badges?.map((badge: any, index: number) => (
        <Badge key={index} iconName={badge.iconName}>
          {badge.text}
        </Badge>
      ))}
    </div>
  )}
/>
```

## Props

### DataTableProps

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `columns` | `string[]` \| `ColumnDef<TData>[]` | Yes | - | Column definitions. Either string array or TanStack ColumnDef array |
| `data` | `TData[]` | Yes | - | Array of data objects to display |
| `caption` | `unknown` | Yes | - | Table caption for accessibility. Can be any type when used with renderCaption |
| `displayCaption` | `boolean` | No | `true` | Whether to visually display the caption |
| `emptyMessage` | `React.ReactNode` | No | `"No results"` | Message to display when data array is empty |
| `search` | `string` | No | - | Search string for filtering table data. Uses fuzzy filtering across all columns |
| `simplePaginationConfig` | `SimplePaginationConfig` | No | - | Configuration for simple show more/less pagination |
| `paginationConfig` | `PaginationConfig` | No | - | Configuration for complex pagination with page navigation and size selection |
| `renderCell` | `(info: CellContext) => React.ReactNode` | No | - | Custom cell renderer for string columns. Receives TanStack Table cell context |
| `renderCaption` | `(caption: unknown) => React.ReactNode` | No | - | Custom caption renderer. Converts caption data to React elements |

## API-Driven Pattern

This component is designed to work seamlessly with API responses. Here's a complete example:

```tsx
import { DataTable } from '@sproutsocial/seeds-react-data-table';
import { Badge } from '@sproutsocial/seeds-react-badge';

// API Response Structure
interface APIResponse {
  caption: {
    title: string;
    badges: Array<{ text: string; iconName?: string }>;
  };
  columns: string[];
  data: Array<Record<string, unknown>>;
}

// Component Renderer
function ComponentRenderer({ apiResponse }: { apiResponse: APIResponse }) {
  const renderCaption = (captionData: any) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
      <span>{captionData.title}</span>
      {captionData.badges?.map((badge: any, index: number) => (
        <Badge key={index} iconName={badge.iconName}>
          {badge.text}
        </Badge>
      ))}
    </div>
  );

  const renderCell = (info: any) => {
    const value = info.getValue();

    // Handle typed cells from API
    if (typeof value === 'object' && value !== null && 'type' in value) {
      const cellData = value as any;

      switch (cellData.type) {
        case 'link':
          return <a href={cellData.href}>{cellData.value}</a>;
        case 'badge':
          return <Badge badgeColor={cellData.color}>{cellData.value}</Badge>;
        case 'status':
          return (
            <Badge
              iconName={cellData.active ? 'circle-check-solid' : 'circle-x-outline'}
              badgeColor={cellData.active ? 'green' : 'red'}
            >
              {cellData.value}
            </Badge>
          );
        default:
          return cellData.value;
      }
    }

    return value;
  };

  return (
    <DataTable
      columns={apiResponse.columns}
      data={apiResponse.data}
      caption={apiResponse.caption}
      renderCaption={renderCaption}
      renderCell={renderCell}
    />
  );
}
```

## Search and Filtering

DataTable supports fuzzy filtering across all columns using TanStack Table's built-in filtering. Simply pass a search string to filter the data:

### Basic Search

```tsx
import { DataTable } from '@sproutsocial/seeds-react-data-table';
import { useState } from 'react';

function SearchableTable() {
  const [searchText, setSearchText] = useState('');

  const data = [
    { name: 'John Doe', email: 'john@example.com', department: 'Engineering' },
    { name: 'Jane Smith', email: 'jane@example.com', department: 'Design' },
    { name: 'Bob Johnson', email: 'bob@example.com', department: 'Engineering' },
  ];

  return (
    <div>
      <input
        type="search"
        placeholder="Search..."
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
      />
      <DataTable
        columns={['Name', 'Email', 'Department']}
        data={data}
        caption="Team Members"
        search={searchText}
        emptyMessage="No team members found"
      />
    </div>
  );
}
```

### Search Features

- **Fuzzy Matching**: Finds approximate matches, not just exact ones
- **Case Insensitive**: Searches ignore letter case
- **Multi-Column**: Searches across all columns automatically
- **Pagination Reset**: Automatically resets to page 1 when search changes

### Search with Pagination

Search works seamlessly with pagination - filtered results are paginated automatically:

```tsx
<DataTable
  columns={['Name', 'Email', 'Department']}
  data={largeDataset}
  caption="User Directory"
  search={searchText}
  simplePaginationConfig={{
    initialVisibleRows: 10,
    expandBy: 10,
    showMoreText: 'Show More',
    showLessText: 'Show Less',
    showAllText: 'Show All',
  }}
/>
```

## Sorting

All columns support sorting by default. Click column headers to sort:

- First click: Sort ascending
- Second click: Sort descending
- Third click: Remove sort

To disable sorting for specific columns when using TanStack ColumnDef:

```tsx
const columns: ColumnDef<Person>[] = [
  {
    accessorKey: 'name',
    header: 'Name',
    enableSorting: false, // Disable sorting for this column
  },
];
```

## Empty State

Display a custom message when no data is available:

```tsx
<DataTable
  columns={['Name', 'Age']}
  data={[]}
  caption="Users"
  emptyMessage="No users found. Try adjusting your filters."
/>
```

## TypeScript

The component is fully typed with generics:

```tsx
interface User {
  id: string;
  name: string;
  email: string;
}

// Type is inferred from data
<DataTable<User>
  columns={['Name', 'Email']}
  data={users}
  caption="Users"
/>
```

## Accessibility

- **Required Caption**: All tables must have a caption for screen readers
- **Keyboard Navigation**: Full keyboard support for sorting
- **ARIA Labels**: Proper ARIA attributes for table structure
- **Screen Reader Support**: Sort state announced to screen readers

## When to Use

### Use DataTable when:
- You need sorting functionality
- You want automatic camelCase column mapping
- You need custom cell rendering based on API data
- You require type-safe column definitions
- Dataset is < 100 rows

### Use TableV2 instead when:
- You have simple string/ReactNode arrays
- You don't need sorting
- You want maximum simplicity

### Use DataTableVirtualized instead when:
- Dataset is > 100 rows
- Performance is critical
- You need smooth scrolling with large data

## Related Components

- **TableV2**: Simple tables with string/ReactNode arrays
- **DataTableVirtualized**: Virtualized tables for large datasets (> 100 rows)

## Examples

Check out the Storybook examples for more patterns:
- Simple string columns
- Advanced TanStack columns
- Custom cell rendering
- API-driven rendering
- Caption with badges
- Color-coded cells
- Empty states