<commit_before>```react
<AllFeaturesTable />
```<commit_msg>docs - Include code to "all features" example<commit_after>The following example uses majority of Reactabular's features and combines them into a single example.

```react
<AllFeaturesTable />
```

```code
lang: jsx
---
import React from 'react';
import { compose } from 'redux';
import findIndex from 'lodash/findIndex';
import orderBy from 'lodash/orderBy';
import keys from 'lodash/keys';
import values from 'lodash/values';
import transform from 'lodash/transform';

import {
  Table, search, editors, sort, transforms, formatters
} from 'reactabular';
import {
  ColumnFilters, Paginator, PrimaryControls, generateData, paginate
} from './helpers';
import countries from './data/countries';

const schema = {
  type: 'object',
  properties: {
    id: {
      type: 'string'
    },
    name: {
      type: 'string'
    },
    position: {
      type: 'string'
    },
    salary: {
      type: 'integer'
    },
    boss: {
      $ref: '#/definitions/boss'
    },
    country: {
      type: 'string',
      enum: keys(countries),
      enumNames: values(countries)
    },
    active: {
      type: 'boolean'
    }
  },
  required: ['id', 'name', 'position', 'salary', 'boss', 'country', 'active'],
  definitions: {
    boss: {
      type: 'object',
      properties: {
        name: {
          type: 'string'
        }
      },
      required: ['name']
    }
  }
};
const data = generateData(100, schema);

class AllFeaturesTable extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      editedCell: null, // currently edited cell
      data, // initial data
      query: {}, // search query
      sortingColumns: null, // reference to the sorting columns
      columns: this.getColumns(), // initial columns
      pagination: { // initial pagination settings
        page: 1,
        perPage: 10
      }
    };

    this.onRowSelected = this.onRowSelected.bind(this);
    this.onSearch = this.onSearch.bind(this);
    this.onSelect = this.onSelect.bind(this);
    this.onPerPage = this.onPerPage.bind(this);
    this.onRemove = this.onRemove.bind(this);
  }
  getColumns() {
    const highlight = column => formatters.highlight(value => (
      search.matches({
        value,
        query: this.state.query[column] || this.state.query.all
      })
    ));
    const editable = transforms.edit({
      getEditId: ({ rowData, property }) => `${rowData.id}-${property}`,
      getEditProperty: () => this.state.editedCell,
      onActivate: idx => this.setState({ editedCell: idx }),
      onValue: ({ value, rowData, property }) => {
        const idx = findIndex(this.state.data, { id: rowData.id });

        this.state.data[idx][property] = value;

        this.setState({
          editedCell: null,
          data: this.state.data
        });
      }
    });
    const sortable = transforms.sort({
      getSortingColumns: () => this.state.sortingColumns || [],
      onSort: selectedColumn => {
        this.setState({
          sortingColumns: sort.byColumns({ // sort.byColumn would work too
            sortingColumns: this.state.sortingColumns,
            selectedColumn
          })
        });
      }
    });
    const sortableHeader = sortHeader(() => this.state.sortingColumns);

    return [
      {
        header: {
          label: 'Name',
          format: (name, extraParameters) => (
            <div style={{ display: 'inline' }}>
              <input
                type="checkbox"
                onClick={() => console.log('clicked')}
                style={{ width: '20px' }}
              />
              {sortableHeader(sortable('name'))(name, extraParameters)}
            </div>
          )
        },
        cell: {
          property: 'name',
          transforms: [editable(editors.input())],
          format: highlight('name')
        }
      },
      {
        header: {
          label: 'Position',
          format: sortableHeader(sortable('position'))
        },
        cell: {
          property: 'position',
          transforms: [editable(editors.input())],
          format: highlight('position')
        }
      },
      {
        header: {
          label: 'Boss',
          format: sortableHeader(sortable('boss.name'))
        },
        cell: {
          property: 'boss.name',
          transforms: [editable(editors.input())],
          format: highlight('boss.name')
        }
      },
      {
        header: {
          label: 'Country',
          format: sortableHeader(sortable('country'))
        },
        cell: {
          property: 'country',
          transforms: [editable(
            editors.dropdown({
              options: transform(countries, (result, name, value) => {
                result.push({ value, name });
              }, [])
            })
          )],
          format: highlight('country'),
          resolve: country => countries[country]
        }
      },
      {
        header: {
          label: 'Salary',
          format: sortableHeader(sortable('salary'))
        },
        cell: {
          property: 'salary',
          transforms: [editable(editors.input({ props: { type: 'number' } }))],
          format: salary => (
            <span onDoubleClick={() => alert(`salary is ${salary}`)}>
              {highlight('salary')(salary)}
            </span>
          )
        }
      },
      {
        header: {
          label: 'Active',
          format: sortableHeader(sortable('active'))
        },
        cell: {
          property: 'active',
          transforms: [editable(editors.boolean())],
          format: active => active && <span>&#10003;</span>
        }
      },
      {
        cell: {
          format: (value, { rowData }) => (
            <span
              className="remove"
              onClick={() => this.onRemove(rowData.id)} style={{ cursor: 'pointer' }}
            >
              &#10007;
            </span>
          )
        }
      }
    ];
  }
  render() {
    const {
      columns, data, pagination, sortingColumns, query
    } = this.state;
    const paginated = compose(
      paginate(pagination),
      sort.sorter({ columns, sortingColumns, sort: orderBy }),
      search.multipleColumns({ columns, query })
    )(data);

    return (
      <div>
        <PrimaryControls
          className="controls"
          perPage={pagination.perPage}
          columns={columns}
          data={data}
          onPerPage={this.onPerPage}
          onSearch={this.onSearch}
        />

        <Table.Provider
          className="pure-table pure-table-striped"
          columns={columns}
          data={paginated.data}
          rowKey="id"
        >
          <Table.Header>
            <ColumnFilters columns={columns} onChange={this.onSearch} />
          </Table.Header>

          <Table.Body
            row={(row, rowIndex) => ({
              className: rowIndex % 2 ? 'odd-row' : 'even-row',
              onClick: () => this.onRowSelected(row)
            })}
          />

          <tfoot>
            <tr>
              <td>
                You could show sums etc. here in the customizable footer.
              </td>
              <td></td>
              <td></td>
              <td></td>
              <td>Total salary: {
                paginated.data.reduce((a, b) => a + b.salary, 0)
              }</td>
              <td></td>
              <td></td>
            </tr>
          </tfoot>
        </Table.Provider>

        <div className="controls">
          <Paginator
            pagination={pagination}
            pages={paginated.amount}
            onSelect={this.onSelect}
          />
        </div>
      </div>
    );
  }
  onRowSelected(row) {
    console.log('clicked row', row);
  }
  onSearch(query) {
    this.setState({
      editedCell: null, // reset edits
      query
    });
  }
  onSelect(page) {
    const pages = Math.ceil(
      this.state.data.length / this.state.pagination.perPage
    );

    this.setState({
      pagination: {
        ...this.state.pagination,
        page: Math.min(Math.max(page, 1), pages)
      }
    });
  }
  onPerPage(value) {
    this.setState({
      pagination: {
        ...this.state.pagination,
        perPage: parseInt(value, 10)
      }
    });
  }
  onRemove(id) {
    const idx = findIndex(this.state.data, { id });

    // this could go through flux etc.
    this.state.data.splice(idx, 1);

    this.setState({
      data: this.state.data
    });
  }
}

function sortHeader(getSortingColumns) {
  return sortable => (value, { column }) => {
    const property = column.cell && column.cell.property;
    const sortingColumns = getSortingColumns();
    const idx = findIndex(sortingColumns, { property });

    return (
      <div style={{ display: 'inline' }}>
        <span className="value">{value}</span>
        {sortable.toFormatter({ props: { style: { float: 'right' } } })}
        {idx >= 0 &&
          <span className="sort-order" style={{ marginLeft: '0.5em', float: 'right' }}>
            {idx + 1}
          </span>
        }
      </div>
    );
  };
}

export default AllFeaturesTable;
```
{'fraction_non_alphanumeric': 0.14634680687917626, 'fraction_numerical': 0.0030671486471683645, 'mean_word_length': 1.764153799576143, 'pattern_counts': {'":': 0, '<': 50, '<?xml version=': 0, '>': 74, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2335636', 'n_tokens_mistral': 2690, 'n_tokens_neox': 2646, 'n_words': 543}