# Table

> A flexible data table component with sorting, filtering, pagination, and search functionality.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/table
- Markdown: https://ui.ahmedbna.com/docs/components/table.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/table.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/table.json
- Install: `npx bna-ui add table`
- npm dependencies: `expo-haptics`, `lucide-react-native`, `react-native-reanimated`, `react-native-svg`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `useHaptics`, `text`, `view`, `icon`, `spinner`, `button`
- Preview recording: https://demo.ahmedbna.com/0293-table-demo.MP4

---

**Example:** A basic data table with sample data

```tsx
// components/demo/table/table-demo.tsx
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
  status: 'Active' | 'Inactive';
}

const sampleData: User[] = [
  {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    role: 'Admin',
    status: 'Active',
  },
  {
    id: 2,
    name: 'Jane Smith',
    email: 'jane@example.com',
    role: 'User',
    status: 'Active',
  },
  {
    id: 3,
    name: 'Bob Johnson',
    email: 'bob@example.com',
    role: 'Manager',
    status: 'Inactive',
  },
  {
    id: 4,
    name: 'Alice Brown',
    email: 'alice@example.com',
    role: 'User',
    status: 'Active',
  },
  {
    id: 5,
    name: 'Charlie Wilson',
    email: 'charlie@example.com',
    role: 'Admin',
    status: 'Active',
  },
];

const columns: TableColumn<User>[] = [
  {
    id: 'name',
    header: 'Name',
    accessorKey: 'name',
    sortable: true,
    filterable: true,
  },
  {
    id: 'email',
    header: 'Email',
    accessorKey: 'email',
    sortable: true,
    filterable: true,
  },
  {
    id: 'role',
    header: 'Role',
    accessorKey: 'role',
    sortable: true,
    filterable: true,
  },
  {
    id: 'status',
    header: 'Status',
    accessorKey: 'status',
    sortable: true,
    filterable: true,
  },
];

export function TableDemo() {
  return (
    <Table
      data={sampleData}
      columns={columns}
      pageSize={5}
      searchPlaceholder='Search users...'
    />
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add table
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install lucide-react-native
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/table.tsx
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { BORDER_RADIUS, FONT_SIZE, HEIGHT } from '@/theme/globals';
import {
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
  ChevronUp,
  Search,
} from 'lucide-react-native';
import React, { useMemo, useState } from 'react';
import {
  FlatList,
  ScrollView,
  TextInput,
  TextStyle,
  TouchableOpacity,
  ViewStyle,
} from 'react-native';

// Types
export interface TableColumn<T = any> {
  id: string;
  header: string;
  accessorKey: string;
  sortable?: boolean;
  filterable?: boolean;
  width?: number | string;
  minWidth?: number;
  cell?: (value: any, row: T) => React.ReactNode;
  headerCell?: () => React.ReactNode;
  align?: 'left' | 'center' | 'right';
}

export interface TableProps<T = any> {
  data: T[];
  columns: TableColumn<T>[];
  pagination?: boolean;
  pageSize?: number;
  searchable?: boolean;
  searchPlaceholder?: string;
  loading?: boolean;
  emptyMessage?: string;
  style?: ViewStyle;
  headerStyle?: ViewStyle;
  rowStyle?: ViewStyle;
  cellStyle?: ViewStyle;
  onRowPress?: (row: T, index: number) => void;
  sortable?: boolean;
  filterable?: boolean;
}

type SortDirection = 'asc' | 'desc' | null;

interface SortState {
  column: string | null;
  direction: SortDirection;
}

export function Table<T = any>({
  data,
  columns,
  pagination = true,
  pageSize = 10,
  searchable = true,
  searchPlaceholder = 'Search...',
  loading = false,
  emptyMessage = 'No data available',
  style,
  headerStyle,
  rowStyle,
  cellStyle,
  onRowPress,
  sortable = true,
  filterable = true,
}: TableProps<T>) {
  // Theme colors
  const borderColor = useColor('border');
  const textColor = useColor('text');
  const mutedColor = useColor('textMuted');
  const cardColor = useColor('card');
  const primaryColor = useColor('primary');

  // State
  const [currentPage, setCurrentPage] = useState(1);
  const [searchQuery, setSearchQuery] = useState('');
  const [sortState, setSortState] = useState<SortState>({
    column: null,
    direction: null,
  });

  // Filter and sort data
  const filteredAndSortedData = useMemo(() => {
    let processedData = [...data];

    // Apply search filter
    if (searchQuery && filterable) {
      processedData = processedData.filter((row) =>
        columns.some((column) => {
          if (!column.filterable) return false;
          const value = (row as any)[column.accessorKey];
          return String(value || '')
            .toLowerCase()
            .includes(searchQuery.toLowerCase());
        })
      );
    }

    // Apply sorting
    if (sortState.column && sortState.direction && sortable) {
      processedData.sort((a, b) => {
        const aValue = (a as any)[sortState.column!];
        const bValue = (b as any)[sortState.column!];

        if (aValue === null || aValue === undefined) return 1;
        if (bValue === null || bValue === undefined) return -1;

        if (typeof aValue === 'string' && typeof bValue === 'string') {
          const comparison = aValue.localeCompare(bValue);
          return sortState.direction === 'asc' ? comparison : -comparison;
        }

        if (aValue < bValue) return sortState.direction === 'asc' ? -1 : 1;
        if (aValue > bValue) return sortState.direction === 'asc' ? 1 : -1;
        return 0;
      });
    }

    return processedData;
  }, [data, searchQuery, sortState, columns, filterable, sortable]);

  // Pagination
  const totalPages = pagination
    ? Math.ceil(filteredAndSortedData.length / pageSize)
    : 1;
  const startIndex = pagination ? (currentPage - 1) * pageSize : 0;
  const endIndex = pagination
    ? startIndex + pageSize
    : filteredAndSortedData.length;
  const paginatedData = filteredAndSortedData.slice(startIndex, endIndex);

  // Handlers
  const handleSort = (columnId: string) => {
    if (!sortable) return;

    const column = columns.find((col) => col.id === columnId);
    if (!column?.sortable) return;

    setSortState((prev) => {
      if (prev.column === columnId) {
        // Cycle through: asc -> desc -> null
        const newDirection: SortDirection =
          prev.direction === 'asc'
            ? 'desc'
            : prev.direction === 'desc'
              ? null
              : 'asc';

        return {
          column: newDirection ? columnId : null,
          direction: newDirection,
        };
      } else {
        return { column: columnId, direction: 'asc' };
      }
    });
  };

  const handlePageChange = (page: number) => {
    setCurrentPage(Math.max(1, Math.min(page, totalPages)));
  };

  const renderSortIcon = (columnId: string) => {
    if (!sortable) return null;

    const column = columns.find((col) => col.id === columnId);
    if (!column?.sortable) return null;

    if (sortState.column !== columnId) {
      return (
        <ChevronUp size={16} color={mutedColor} style={{ opacity: 0.3 }} />
      );
    }

    return sortState.direction === 'asc' ? (
      <ChevronUp size={16} color={primaryColor} />
    ) : (
      <ChevronDown size={16} color={primaryColor} />
    );
  };

  const renderCell = (column: TableColumn<T>, row: T, rowIndex: number) => {
    const value = (row as any)[column.accessorKey];
    const cellContent = column.cell
      ? column.cell(value, row)
      : String(value || '');

    const alignStyle: TextStyle = {
      textAlign: column.align || 'left',
    };

    return (
      <View
        key={column.id}
        style={[
          {
            flex: column.width ? 0 : 1,
            width: column.width as any,
            minWidth: column.minWidth || 100,
            paddingHorizontal: 18,
            paddingVertical: 16,
            justifyContent: 'center',
          },
          cellStyle,
        ]}
      >
        {typeof cellContent === 'string' ? (
          <Text style={[{ fontSize: FONT_SIZE }, alignStyle]}>
            {cellContent}
          </Text>
        ) : (
          cellContent
        )}
      </View>
    );
  };

  const renderHeader = () => (
    <View
      style={[
        {
          flexDirection: 'row',
          backgroundColor: cardColor,
          borderBottomWidth: 1,
          borderBottomColor: borderColor,
        },
        headerStyle,
      ]}
    >
      {columns.map((column) => (
        <TouchableOpacity
          key={column.id}
          style={{
            flex: column.width ? 0 : 1,
            width: column.width as any,
            minWidth: column.minWidth || 100,
            paddingHorizontal: 18,
            paddingVertical: 16,
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent:
              column.align === 'center'
                ? 'center'
                : column.align === 'right'
                  ? 'flex-end'
                  : 'flex-start',
          }}
          onPress={() => handleSort(column.id)}
          disabled={!column.sortable || !sortable}
          accessibilityRole={column.sortable && sortable ? 'button' : undefined}
          accessibilityLabel={
            column.sortable && sortable
              ? `${column.header}, ${
                  sortState.column === column.id
                    ? sortState.direction === 'asc'
                      ? 'sorted ascending'
                      : 'sorted descending'
                    : 'not sorted'
                }`
              : column.header
          }
        >
          {column.headerCell ? (
            column.headerCell()
          ) : (
            <>
              <Text
                variant='subtitle'
                style={{
                  marginRight: column.sortable && sortable ? 4 : 0,
                  textAlign: column.align || 'left',
                }}
              >
                {column.header}
              </Text>
              {renderSortIcon(column.id)}
            </>
          )}
        </TouchableOpacity>
      ))}
    </View>
  );

  const renderRow = (row: T, index: number) => (
    <TouchableOpacity
      key={index}
      style={[
        {
          flexDirection: 'row',
          backgroundColor: cardColor,
          borderBottomWidth: 1,
          borderBottomColor: borderColor,
        },
        rowStyle,
      ]}
      onPress={() => onRowPress?.(row, index)}
      disabled={!onRowPress}
      activeOpacity={onRowPress ? 0.7 : 1}
    >
      {columns.map((column) => renderCell(column, row, index))}
    </TouchableOpacity>
  );

  const renderPagination = () => {
    if (!pagination || totalPages <= 1) return null;

    return (
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'space-between',
          alignItems: 'center',
          paddingHorizontal: 16,
          paddingVertical: 18,
          backgroundColor: cardColor,
          borderTopWidth: 1,
          borderTopColor: borderColor,
        }}
      >
        <Text variant='caption'>
          Page {currentPage} of {totalPages} ({filteredAndSortedData.length}{' '}
          total)
        </Text>

        <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
          <Button
            variant='outline'
            size='sm'
            onPress={() => handlePageChange(1)}
            disabled={currentPage === 1}
          >
            <ChevronsLeft
              size={16}
              color={currentPage === 1 ? mutedColor : textColor}
            />
          </Button>

          <Button
            variant='outline'
            size='sm'
            onPress={() => handlePageChange(currentPage - 1)}
            disabled={currentPage === 1}
          >
            <ChevronLeft
              size={16}
              color={currentPage === 1 ? mutedColor : textColor}
            />
          </Button>

          <Button
            variant='outline'
            size='sm'
            onPress={() => handlePageChange(currentPage + 1)}
            disabled={currentPage === totalPages}
          >
            <ChevronRight
              size={16}
              color={currentPage === totalPages ? mutedColor : textColor}
            />
          </Button>

          <Button
            variant='outline'
            size='sm'
            onPress={() => handlePageChange(totalPages)}
            disabled={currentPage === totalPages}
          >
            <ChevronsRight
              size={16}
              color={currentPage === totalPages ? mutedColor : textColor}
            />
          </Button>
        </View>
      </View>
    );
  };

  const renderSearchBar = () => {
    if (!searchable || !filterable) return null;

    return (
      <View
        style={{
          flexDirection: 'row',
          alignItems: 'center',
          backgroundColor: cardColor,
          borderBottomWidth: 1,
          borderColor: borderColor,
          paddingHorizontal: 18,
          height: HEIGHT,
          marginVertical: 2,
        }}
      >
        <Search size={16} color={mutedColor} style={{ marginRight: 8 }} />

        <TextInput
          style={{
            flex: 1,
            fontSize: FONT_SIZE,
            color: textColor,
            paddingVertical: 8,
          }}
          placeholder={searchPlaceholder}
          placeholderTextColor={mutedColor}
          value={searchQuery}
          onChangeText={setSearchQuery}
        />
      </View>
    );
  };

  const renderEmptyState = () => (
    <View
      style={{
        padding: 32,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: cardColor,
      }}
    >
      <Text variant='body' style={{ color: mutedColor }}>
        {emptyMessage}
      </Text>
    </View>
  );

  const renderLoadingState = () => (
    <View
      style={{
        padding: 32,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: cardColor,
      }}
    >
      <Text variant='body' style={{ color: mutedColor }}>
        Loading...
      </Text>
    </View>
  );

  return (
    <View
      style={[
        {
          width: '100%',
          borderRadius: BORDER_RADIUS,
          borderWidth: 1,
          borderColor: borderColor,
          backgroundColor: cardColor,
          overflow: 'hidden',
        },
        style,
      ]}
    >
      {renderSearchBar()}

      <ScrollView horizontal showsHorizontalScrollIndicator={false}>
        <View style={{ minWidth: '100%' }}>
          {renderHeader()}

          {loading ? (
            renderLoadingState()
          ) : paginatedData.length === 0 ? (
            renderEmptyState()
          ) : (
            <FlatList
              data={paginatedData}
              keyExtractor={(_, index) => String(index)}
              renderItem={({ item, index }) => renderRow(item, index)}
              showsVerticalScrollIndicator={false}
              // Rows aren't bounded by pageSize when pagination={false} —
              // without virtualization this was the only size guard missing
              // from an otherwise generic, potentially large data table.
            />
          )}
        </View>
      </ScrollView>

      {renderPagination()}
    </View>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { Table, TableColumn } from '@/components/ui/table';
```

```tsx
const data = [
  { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
];

const columns: TableColumn[] = [
  { id: 'name', header: 'Name', accessorKey: 'name', sortable: true },
  { id: 'email', header: 'Email', accessorKey: 'email', sortable: true },
  { id: 'role', header: 'Role', accessorKey: 'role', filterable: true },
];

<Table data={data} columns={columns} />;
```

## Examples

#### Basic Table

**Example:** A basic data table with sample data

```tsx
// components/demo/table/table-demo.tsx
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
  status: 'Active' | 'Inactive';
}

const sampleData: User[] = [
  {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    role: 'Admin',
    status: 'Active',
  },
  {
    id: 2,
    name: 'Jane Smith',
    email: 'jane@example.com',
    role: 'User',
    status: 'Active',
  },
  {
    id: 3,
    name: 'Bob Johnson',
    email: 'bob@example.com',
    role: 'Manager',
    status: 'Inactive',
  },
  {
    id: 4,
    name: 'Alice Brown',
    email: 'alice@example.com',
    role: 'User',
    status: 'Active',
  },
  {
    id: 5,
    name: 'Charlie Wilson',
    email: 'charlie@example.com',
    role: 'Admin',
    status: 'Active',
  },
];

const columns: TableColumn<User>[] = [
  {
    id: 'name',
    header: 'Name',
    accessorKey: 'name',
    sortable: true,
    filterable: true,
  },
  {
    id: 'email',
    header: 'Email',
    accessorKey: 'email',
    sortable: true,
    filterable: true,
  },
  {
    id: 'role',
    header: 'Role',
    accessorKey: 'role',
    sortable: true,
    filterable: true,
  },
  {
    id: 'status',
    header: 'Status',
    accessorKey: 'status',
    sortable: true,
    filterable: true,
  },
];

export function TableDemo() {
  return (
    <Table
      data={sampleData}
      columns={columns}
      pageSize={5}
      searchPlaceholder='Search users...'
    />
  );
}
```

#### Sortable Columns

**Example:** Table with sortable columns

```tsx
// components/demo/table/table-sortable.tsx
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  rating: number;
}

const products: Product[] = [
  {
    id: 1,
    name: 'Laptop Pro',
    price: 1299.99,
    category: 'Electronics',
    inStock: true,
    rating: 4.5,
  },
  {
    id: 2,
    name: 'Wireless Mouse',
    price: 29.99,
    category: 'Electronics',
    inStock: true,
    rating: 4.2,
  },
  {
    id: 3,
    name: 'Coffee Mug',
    price: 12.99,
    category: 'Kitchen',
    inStock: false,
    rating: 4.0,
  },
  {
    id: 4,
    name: 'Desk Chair',
    price: 199.99,
    category: 'Furniture',
    inStock: true,
    rating: 4.7,
  },
  {
    id: 5,
    name: 'Notebook',
    price: 5.99,
    category: 'Office',
    inStock: true,
    rating: 3.8,
  },
  {
    id: 6,
    name: 'Smartphone',
    price: 699.99,
    category: 'Electronics',
    inStock: true,
    rating: 4.3,
  },
];

const columns: TableColumn<Product>[] = [
  {
    id: 'name',
    header: 'Product Name',
    accessorKey: 'name',
    sortable: true,
    filterable: true,
    minWidth: 150,
  },
  {
    id: 'price',
    header: 'Price',
    accessorKey: 'price',
    sortable: true,
    align: 'right',
    cell: (value) => `$${value.toFixed(2)}`,
    minWidth: 100,
  },
  {
    id: 'category',
    header: 'Category',
    accessorKey: 'category',
    sortable: true,
    filterable: true,
    minWidth: 120,
  },
  {
    id: 'inStock',
    header: 'In Stock',
    accessorKey: 'inStock',
    sortable: true,
    align: 'center',
    cell: (value) => (value ? '✅' : '❌'),
    minWidth: 100,
  },
  {
    id: 'rating',
    header: 'Rating',
    accessorKey: 'rating',
    sortable: true,
    align: 'center',
    cell: (value) => `⭐ ${value}`,
    minWidth: 100,
  },
];

export function TableSortable() {
  return (
    <Table
      data={products}
      columns={columns}
      pageSize={4}
      searchPlaceholder='Search products...'
    />
  );
}
```

#### Custom Cell Rendering

**Example:** Table with custom cell renderers and formatting

```tsx
// components/demo/table/table-custom-cells.tsx
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Table, TableColumn } from '@/components/ui/table';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

interface Employee {
  id: number;
  name: string;
  email: string;
  department: string;
  salary: number;
  avatar?: string;
  joinDate: string;
  status: 'Active' | 'On Leave' | 'Terminated';
}

const employees: Employee[] = [
  {
    id: 1,
    name: 'Sarah Johnson',
    email: 'sarah.j@company.com',
    department: 'Engineering',
    salary: 95000,
    avatar: 'https://avatars.githubusercontent.com/u/1?v=4',
    joinDate: '2022-01-15',
    status: 'Active',
  },
  {
    id: 2,
    name: 'Mike Chen',
    email: 'mike.c@company.com',
    department: 'Design',
    salary: 78000,
    joinDate: '2023-03-20',
    status: 'Active',
  },
  {
    id: 3,
    name: 'Emma Davis',
    email: 'emma.d@company.com',
    department: 'Marketing',
    salary: 65000,
    avatar: 'https://avatars.githubusercontent.com/u/2?v=4',
    joinDate: '2021-11-08',
    status: 'On Leave',
  },
  {
    id: 4,
    name: 'James Wilson',
    email: 'james.w@company.com',
    department: 'Sales',
    salary: 72000,
    joinDate: '2020-09-12',
    status: 'Terminated',
  },
];

const columns: TableColumn<Employee>[] = [
  {
    id: 'employee',
    header: 'Employee',
    accessorKey: 'name',
    sortable: true,
    filterable: true,
    minWidth: 200,
    cell: (value, row) => (
      <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
        <Avatar size={32}>
          {row.avatar && <AvatarImage source={{ uri: row.avatar }} />}
          <AvatarFallback>
            {row.name
              .split(' ')
              .map((n) => n[0])
              .join('')}
          </AvatarFallback>
        </Avatar>
        <View>
          <Text variant='body' style={{ fontWeight: '600' }}>
            {row.name}
          </Text>
          <Text variant='caption' style={{ opacity: 0.7 }}>
            {row.email}
          </Text>
        </View>
      </View>
    ),
  },
  {
    id: 'department',
    header: 'Department',
    accessorKey: 'department',
    sortable: true,
    filterable: true,
    minWidth: 120,
  },
  {
    id: 'salary',
    header: 'Salary',
    accessorKey: 'salary',
    sortable: true,
    align: 'right',
    minWidth: 120,
    cell: (value) => (
      <Text variant='body' style={{ fontWeight: '600' }}>
        ${value.toLocaleString()}
      </Text>
    ),
  },
  {
    id: 'joinDate',
    header: 'Join Date',
    accessorKey: 'joinDate',
    sortable: true,
    align: 'center',
    minWidth: 120,
    cell: (value) => new Date(value).toLocaleDateString(),
  },
  {
    id: 'status',
    header: 'Status',
    accessorKey: 'status',
    sortable: true,
    filterable: true,
    align: 'center',
    minWidth: 120,
    cell: (value) => (
      <Badge
        variant={
          value === 'Active'
            ? 'default'
            : value === 'On Leave'
              ? 'secondary'
              : 'destructive'
        }
      >
        {value}
      </Badge>
    ),
  },
];

export function TableCustomCells() {
  return (
    <Table
      data={employees}
      columns={columns}
      pageSize={3}
      searchPlaceholder='Search employees...'
    />
  );
}
```

#### Pagination

**Example:** Table with pagination controls

```tsx
// components/demo/table/table-pagination.tsx
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';

interface Order {
  id: string;
  customer: string;
  product: string;
  amount: number;
  date: string;
  status: 'Pending' | 'Completed' | 'Cancelled';
}

// Generate sample data
const generateOrders = (count: number): Order[] => {
  const customers = [
    'John Doe',
    'Jane Smith',
    'Bob Johnson',
    'Alice Brown',
    'Charlie Wilson',
    'Diana Ross',
    'Frank Miller',
    'Grace Lee',
  ];
  const products = [
    'Laptop',
    'Mouse',
    'Keyboard',
    'Monitor',
    'Headphones',
    'Webcam',
    'Tablet',
    'Phone',
  ];
  const statuses: Order['status'][] = ['Pending', 'Completed', 'Cancelled'];

  return Array.from({ length: count }, (_, i) => ({
    id: `ORD-${String(i + 1).padStart(4, '0')}`,
    customer: customers[i % customers.length],
    product: products[i % products.length],
    amount: Math.floor(Math.random() * 1000) + 50,
    date: new Date(Date.now() - Math.random() * 90 * 24 * 60 * 60 * 1000)
      .toISOString()
      .split('T')[0],
    status: statuses[Math.floor(Math.random() * statuses.length)],
  }));
};

const orders = generateOrders(50);

const columns: TableColumn<Order>[] = [
  {
    id: 'id',
    header: 'Order ID',
    accessorKey: 'id',
    sortable: true,
    filterable: true,
    minWidth: 120,
  },
  {
    id: 'customer',
    header: 'Customer',
    accessorKey: 'customer',
    sortable: true,
    filterable: true,
    minWidth: 150,
  },
  {
    id: 'product',
    header: 'Product',
    accessorKey: 'product',
    sortable: true,
    filterable: true,
    minWidth: 120,
  },
  {
    id: 'amount',
    header: 'Amount',
    accessorKey: 'amount',
    sortable: true,
    align: 'right',
    minWidth: 100,
    cell: (value) => `$${value.toFixed(2)}`,
  },
  {
    id: 'date',
    header: 'Date',
    accessorKey: 'date',
    sortable: true,
    align: 'center',
    minWidth: 120,
  },
  {
    id: 'status',
    header: 'Status',
    accessorKey: 'status',
    sortable: true,
    filterable: true,
    align: 'center',
    minWidth: 120,
  },
];

export function TablePagination() {
  return (
    <Table
      data={orders}
      columns={columns}
      pageSize={8}
      searchPlaceholder='Search orders...'
      pagination={true}
    />
  );
}
```

#### Search and Filter

**Example:** Table with search functionality

```tsx
// components/demo/table/table-search.tsx
import { Table, TableColumn } from '@/components/ui/table';
import React from 'react';

interface Book {
  id: number;
  title: string;
  author: string;
  genre: string;
  year: number;
  isbn: string;
  pages: number;
}

const books: Book[] = [
  {
    id: 1,
    title: 'The Great Gatsby',
    author: 'F. Scott Fitzgerald',
    genre: 'Fiction',
    year: 1925,
    isbn: '978-0-7432-7356-5',
    pages: 180,
  },
  {
    id: 2,
    title: 'To Kill a Mockingbird',
    author: 'Harper Lee',
    genre: 'Fiction',
    year: 1960,
    isbn: '978-0-06-112008-4',
    pages: 281,
  },
  {
    id: 3,
    title: '1984',
    author: 'George Orwell',
    genre: 'Dystopian',
    year: 1949,
    isbn: '978-0-452-28423-4',
    pages: 328,
  },
  {
    id: 4,
    title: 'Pride and Prejudice',
    author: 'Jane Austen',
    genre: 'Romance',
    year: 1813,
    isbn: '978-0-14-143951-8',
    pages: 432,
  },
  {
    id: 5,
    title: 'The Catcher in the Rye',
    author: 'J.D. Salinger',
    genre: 'Fiction',
    year: 1951,
    isbn: '978-0-316-76948-0',
    pages: 277,
  },
  {
    id: 6,
    title: 'Lord of the Flies',
    author: 'William Golding',
    genre: 'Fiction',
    year: 1954,
    isbn: '978-0-571-05686-2',
    pages: 224,
  },
  {
    id: 7,
    title: 'The Hobbit',
    author: 'J.R.R. Tolkien',
    genre: 'Fantasy',
    year: 1937,
    isbn: '978-0-547-92822-7',
    pages: 366,
  },
  {
    id: 8,
    title: "Harry Potter and the Sorcerer's Stone",
    author: 'J.K. Rowling',
    genre: 'Fantasy',
    year: 1997,
    isbn: '978-0-439-70818-8',
    pages: 309,
  },
  {
    id: 9,
    title: 'The Da Vinci Code',
    author: 'Dan Brown',
    genre: 'Mystery',
    year: 2003,
    isbn: '978-0-307-47427-5',
    pages: 689,
  },
  {
    id: 10,
    title: 'Brave New World',
    author: 'Aldous Huxley',
    genre: 'Science Fiction',
    year: 1932,
    isbn: '978-0-06-085052-4',
    pages: 268,
  },
];

const columns: TableColumn<Book>[] = [
  {
    id: 'title',
    header: 'Title',
    accessorKey: 'title',
    sortable: true,
    filterable: true,
    minWidth: 200,
  },
  {
    id: 'author',
    header: 'Author',
    accessorKey: 'author',
    sortable: true,
    filterable: true,
    minWidth: 150,
  },
  {
    id: 'genre',
    header: 'Genre',
    accessorKey: 'genre',
    sortable: true,
    filterable: true,
    minWidth: 120,
  },
  {
    id: 'year',
    header: 'Year',
    accessorKey: 'year',
    sortable: true,
    align: 'center',
    minWidth: 80,
  },
  {
    id: 'isbn',
    header: 'ISBN',
    accessorKey: 'isbn',
    filterable: true,
    minWidth: 150,
  },
  {
    id: 'pages',
    header: 'Pages',
    accessorKey: 'pages',
    sortable: true,
    align: 'right',
    minWidth: 80,
  },
];

export function TableSearch() {
  return (
    <Table
      data={books}
      columns={columns}
      pageSize={6}
      searchPlaceholder='Search books by title, author, genre, or ISBN...'
      searchable={true}
      filterable={true}
    />
  );
}
```

#### Loading State

**Example:** Table showing loading state

```tsx
// components/demo/table/table-loading.tsx
import { Button } from '@/components/ui/button';
import { Table, TableColumn } from '@/components/ui/table';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

interface ApiData {
  id: number;
  name: string;
  value: number;
  category: string;
}

const mockData: ApiData[] = [
  { id: 1, name: 'Item A', value: 100, category: 'Type 1' },
  { id: 2, name: 'Item B', value: 250, category: 'Type 2' },
  { id: 3, name: 'Item C', value: 175, category: 'Type 1' },
  { id: 4, name: 'Item D', value: 320, category: 'Type 3' },
];

const columns: TableColumn<ApiData>[] = [
  {
    id: 'name',
    header: 'Name',
    accessorKey: 'name',
    sortable: true,
    filterable: true,
  },
  {
    id: 'value',
    header: 'Value',
    accessorKey: 'value',
    sortable: true,
    align: 'right',
  },
  {
    id: 'category',
    header: 'Category',
    accessorKey: 'category',
    sortable: true,
    filterable: true,
  },
];

export function TableLoading() {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState<ApiData[]>([]);

  const simulateLoading = () => {
    setLoading(true);
    setData([]);

    // Simulate API call
    setTimeout(() => {
      setData(mockData);
      setLoading(false);
    }, 2000);
  };

  const clearData = () => {
    setData([]);
    setLoading(false);
  };

  return (
    <View style={{ gap: 16 }}>
      <View style={{ flexDirection: 'row', gap: 12 }}>
        <Button onPress={simulateLoading} disabled={loading}>
          Load Data
        </Button>
        <Button variant='outline' onPress={clearData} disabled={loading}>
          Clear Data
        </Button>
      </View>

      <Table
        data={data}
        columns={columns}
        loading={loading}
        emptyMessage="Click 'Load Data' to fetch some data"
        pageSize={5}
      />
    </View>
  );
}
```

## API Reference

### Table

The main table component that renders data in a structured format.

| Prop                | Type                              | Default               | Description                                       |
| ------------------- | --------------------------------- | --------------------- | ------------------------------------------------- |
| `data`              | `T[]`                             | -                     | Array of data objects to display in the table.    |
| `columns`           | `TableColumn<T>[]`                | -                     | Array of column definitions.                      |
| `pagination`        | `boolean`                         | `true`                | Whether to enable pagination.                     |
| `pageSize`          | `number`                          | `10`                  | Number of rows per page.                          |
| `searchable`        | `boolean`                         | `true`                | Whether to show the search bar.                   |
| `searchPlaceholder` | `string`                          | `'Search...'`         | Placeholder text for the search input.            |
| `loading`           | `boolean`                         | `false`               | Whether to show loading state.                    |
| `emptyMessage`      | `string`                          | `'No data available'` | Message to show when no data is available.        |
| `style`             | `ViewStyle`                       | -                     | Additional styles for the table container.        |
| `headerStyle`       | `ViewStyle`                       | -                     | Additional styles for the header row.             |
| `rowStyle`          | `ViewStyle`                       | -                     | Additional styles for data rows.                  |
| `cellStyle`         | `ViewStyle`                       | -                     | Additional styles for table cells.                |
| `onRowPress`        | `(row: T, index: number) => void` | -                     | Callback when a row is pressed.                   |
| `sortable`          | `boolean`                         | `true`                | Whether to enable global sorting functionality.   |
| `filterable`        | `boolean`                         | `true`                | Whether to enable global filtering functionality. |

### TableColumn

Configuration object for table columns.

| Prop          | Type                                      | Default  | Description                                        |
| ------------- | ----------------------------------------- | -------- | -------------------------------------------------- |
| `id`          | `string`                                  | -        | Unique identifier for the column.                  |
| `header`      | `string`                                  | -        | Text to display in the column header.              |
| `accessorKey` | `string`                                  | -        | Key to access data from the row object.            |
| `sortable`    | `boolean`                                 | `false`  | Whether this column can be sorted.                 |
| `filterable`  | `boolean`                                 | `false`  | Whether this column is included in search filters. |
| `width`       | `number \| string`                        | -        | Fixed width for the column.                        |
| `minWidth`    | `number`                                  | `100`    | Minimum width for the column.                      |
| `cell`        | `(value: any, row: T) => React.ReactNode` | -        | Custom cell renderer function.                     |
| `headerCell`  | `() => React.ReactNode`                   | -        | Custom header cell renderer function.              |
| `align`       | `'left' \| 'center' \| 'right'`           | `'left'` | Text alignment for the column.                     |

## Features

### Sorting

- Click column headers to sort (supports ascending, descending, and no sort)
- Visual indicators show current sort state
- Multiple data types supported (string, number, date)

### Filtering

- Global search across all filterable columns
- Case-insensitive search
- Real-time filtering as you type

### Pagination

- Configurable page size
- Navigation controls (first, previous, next, last)
- Page information display

### Responsive Design

- Horizontal scrolling for wide tables
- Flexible column widths
- Mobile-friendly touch interactions

### Accessibility

The Table component is built with accessibility in mind:

- Proper semantic structure with TouchableOpacity for interactive elements
- Screen reader support for sort states and pagination
- High contrast colors for better visibility
- Keyboard navigation support where applicable
- Clear loading and empty states

## Performance

For large datasets, consider:

- Implementing server-side pagination
- Using React.memo for custom cell components
- Virtualizing rows for very large datasets
- Debouncing search input for better performance
