import { useState } from "react";
import { Story, Preview, Props, Meta } from "@storybook/addon-docs/blocks";
import { EnumTable } from "../../storybook-components/EnumTable";
import { Table } from "./Table";
import { SORT_DIRECTION } from "./TableContext";
import { Box } from "../Box";
import { Checkbox } from "../Checkbox";
import { ScrollPane } from "../ScrollPane";
import { Button, BUTTON_VARIANT } from "../Button";
import { ICON_TYPE } from "../Icon";

<Meta title="Components/Data/Table" component={Table} />

# Table

The `Table` component is used to display data of varying complexity that may or may not be editable. It includes sorting and filtering functionality as well.

See [`Simple Table`](/?path=/docs/components-simpletable--no-head) for a smaller, more basic table without sorting or filtering.

```jsx
import { Table } from "@aptible/arrow-ds";
```

<Preview>
  <Story name="Table">
    {() => {
      const rowData = [
        {
          id: "1",
          name: "Wendy George",
          email: "wendy.george@healthco.com",
        },
        {
          id: "2",
          name: "Joe Smith",
          email: "joe.smith@healthco.com",
        },
        {
          id: "3",
          name: "Hannah Williams",
          email: "hannah.williams@healthco.com",
        },
      ];
      return (
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell>ID</Table.HeaderCell>
              <Table.HeaderCell>Name</Table.HeaderCell>
              <Table.HeaderCell>Email</Table.HeaderCell>
              <Table.HeaderCell>Extra 1</Table.HeaderCell>
              <Table.HeaderCell>Extra 2</Table.HeaderCell>
              <Table.HeaderCell>Extra 3</Table.HeaderCell>
              <Table.HeaderCell>Extra 4</Table.HeaderCell>
              <Table.HeaderCell>Extra 5</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            {rowData.map((row) => {
              return (
                <Table.Row key={row.id}>
                  <Table.Cell>{row.id}</Table.Cell>
                  <Table.Cell>{row.name}</Table.Cell>
                  <Table.Cell>{row.email}</Table.Cell>
                  <Table.Cell>#1</Table.Cell>
                  <Table.Cell>#2</Table.Cell>
                  <Table.Cell>#3</Table.Cell>
                  <Table.Cell>#4</Table.Cell>
                  <Table.Cell>#5</Table.Cell>
                </Table.Row>
              );
            })}
          </Table.Body>
        </Table>
      );
    }}
  </Story>
</Preview>

## `Table` Custom Props

These are the custom props that extend [`React.HTMLProps<HTMLTableElement>`](/?path=/docs/resources-element-props-table--page).

<Props of={Table} />

### `Table.HeaderCell` Custom Props

These are the custom props that extend [`React.HTMLProps<HTMLTableHeaderCellElement>`](/?path=/docs/resources-element-props-table-header-cell--page).

<Props of={Table.HeaderCell} />

### `Table.Cell` Custom Props

These are the custom props that extend [`React.HTMLProps<HTMLTableCellElement>`](/?path=/docs/resources-element-props-table-cell--page).

<Props of={Table.Cell} />

### Table components without custom props

- `Table.Head` uses[`React.HTMLProps<HTMLTableSectionElement>`](/?path=/docs/resources-element-props-table-section--page)
- `Table.Body` uses [`React.HTMLProps<HTMLTableSectionElement>`](/?path=/docs/resources-element-props-table-section--page)
- `Table.Row` uses [`React.HTMLProps<HTMLTableRowElement>`](/?path=/docs/resources-element-props-table-row--page)

<EnumTable enums={{ SORT_DIRECTION }} />

## Demos

### With sorting

<Preview>
  <Story name="Table with sorting">
    {() => {
      const rowData = [
        {
          id: "1",
          name: "Wendy George",
          email: "wendy.george@healthco.com",
        },
        {
          id: "3",
          name: "Joe Smith",
          email: "joe.smith@healthco.com",
        },
        {
          id: "2",
          name: "Hannah Williams",
          email: "hannah.williams@healthco.com",
        },
      ];
      const [sort, setSort] = useState({
        column: "id",
        direction: SORT_DIRECTION.ASC,
      });
      const handleSort = (columnId) => {
        if (sort.column === columnId && sort.direction === SORT_DIRECTION.ASC) {
          setSort({ column: columnId, direction: SORT_DIRECTION.DESC });
        } else {
          setSort({ column: columnId, direction: SORT_DIRECTION.ASC });
        }
      };
      let sortedRowData = rowData.sort((a, b) =>
        (a[sort.column] || "") > (b[sort.column] || "") ? 1 : -1,
      );
      sortedRowData =
        sort.direction === SORT_DIRECTION.DESC
          ? sortedRowData.reverse()
          : sortedRowData;
      return (
        <Table
          onSort={handleSort}
          sortColumnId={sort.column}
          sortDirection={sort.direction}
        >
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell id="id">ID</Table.HeaderCell>
              <Table.HeaderCell id="name">Name</Table.HeaderCell>
              <Table.HeaderCell id="email">Email</Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            {sortedRowData.map((row) => {
              return (
                <Table.Row key={row.id}>
                  <Table.Cell>{row.id}</Table.Cell>
                  <Table.Cell>{row.name}</Table.Cell>
                  <Table.Cell>{row.email}</Table.Cell>
                </Table.Row>
              );
            })}
          </Table.Body>
        </Table>
      );
    }}
  </Story>
</Preview>

### With filtering

<Preview>
  <Story name="Table with filtering">
    {() => {
      const [filters, setFilters] = useState({
        number: [],
        evenOrOdd: [],
      });
      const [numberFilterOptionSearch, setNumberFilterOptionSearch] = useState(
        "",
      );
      const rowData = [];
      for (let i = 0; i < 10; i += 1) {
        rowData.push({ number: i, evenOrOdd: i % 2 === 0 ? "even" : "odd" });
      }
      const handleFilterOptionChange = (e, column) => {
        if (e.target.checked) {
          setFilters({
            ...filters,
            [column]: [...filters[column], e.target.value],
          });
        } else {
          const index = filters[column].indexOf(e.target.value);
          if (index > -1) {
            const filterColumnCopy = [...filters[column]];
            filterColumnCopy.splice(index, 1);
            setFilters({ ...filters, [column]: filterColumnCopy });
          }
        }
      };
      const FilterOption = ({ option, column, ...rest }) => (
        <Checkbox
          label={option}
          onChange={(e) => handleFilterOptionChange(e, column)}
          value={option}
          {...rest}
        />
      );
      const getReducedFilterOptions = (column) =>
        rowData.reduce((acc, row) => {
          if (numberFilterOptionSearch) {
            if (!`${row[column]}`.includes(numberFilterOptionSearch)) {
              return acc;
            }
          }
          return acc.some((item) => row[column] === item)
            ? acc
            : [...acc, row[column]];
        }, []);
      const getVisibleFilterOptions = (column) => {
        return getReducedFilterOptions(column).map((option) => (
          <FilterOption
            option={`${option}`}
            key={option}
            column={column}
            checked={filters[column].some((filter) => filter === `${option}`)}
          />
        ));
      };
      const handleSelectAllNumberFilters = () => {
        setFilters({
          ...filters,
          number: rowData.map((row) => `${row.number}`),
        });
      };
      const handleClearAllNumberFilters = () => {
        setFilters({ ...filters, number: [] });
      };
      return (
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell
                filterOptions={getVisibleFilterOptions("number")}
                totalFilterOptions={getReducedFilterOptions("number").length}
                filterApplied={filters.number.length !== 0}
                onSelectAllFilters={handleSelectAllNumberFilters}
                onClearFilters={handleClearAllNumberFilters}
                searchInputValue={numberFilterOptionSearch}
                onSearchInputChange={setNumberFilterOptionSearch}
                sortable={false}
              >
                Number
              </Table.HeaderCell>
              <Table.HeaderCell
                sortable={false}
                filterOptions={getVisibleFilterOptions("evenOrOdd")}
                totalFilterOptions={getReducedFilterOptions("evenOrOdd").length}
                filterApplied={filters.evenOrOdd.length !== 0}
                hideFilterSearch
              >
                Even or Odd
              </Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            {rowData
              .filter((row) => {
                if (
                  filters.number.length === 0 &&
                  filters.evenOrOdd.length === 0
                ) {
                  return true;
                }
                const filterMatch = { number: false, evenOrOdd: false };
                Object.keys(row).forEach((column) => {
                  if (
                    filters[column].length === 0 ||
                    filters[column].some(
                      (filter) => filter === `${row[column]}`,
                    )
                  ) {
                    filterMatch[column] = true;
                  }
                });
                return filterMatch.number && filterMatch.evenOrOdd;
              })
              .map((row) => {
                return (
                  <Table.Row key={row.number}>
                    <Table.Cell>{row.number}</Table.Cell>
                    <Table.Cell>{row.evenOrOdd}</Table.Cell>
                  </Table.Row>
                );
              })}
          </Table.Body>
        </Table>
      );
    }}
  </Story>
</Preview>

### With column horizontal scroll

For the table to scroll, it must be wrapped with a ScrollPane.

<Preview>
  <Story name="With Horizontal Scroll">
    <Box className="h-46">
      <ScrollPane>
        <Table>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #1
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #2
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #3
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #4
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #5
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #6
              </Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            <Table.Row>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
            </Table.Row>
            <Table.Row>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>
      </ScrollPane>
    </Box>
  </Story>
</Preview>

### With column horizontal scroll and sticky column

For the table to scroll, it must be wrapped with a ScrollPane.

<Preview>
  <Story name="With Horizontal Scroll and Sticky Column">
    <Box className="h-46">
      <ScrollPane>
        <Table stickyColumn>
          <Table.Head>
            <Table.Row>
              <Table.HeaderCell
                sortable={false}
                columnWidth="50%"
                minColumnWidth="400px"
              >
                #1 Fixed 400px
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #2
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #3
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #4
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #5
              </Table.HeaderCell>
              <Table.HeaderCell sortable={false} minColumnWidth="400px">
                #6
              </Table.HeaderCell>
            </Table.Row>
          </Table.Head>
          <Table.Body>
            <Table.Row>
              <Table.Cell minColumnWidth="400px" columnWidth="50%">
                I am Fixed!
              </Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
            </Table.Row>
            <Table.Row>
              <Table.Cell minColumnWidth="400px" columnWidth="50%">
                I am Fixed!
              </Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
              <Table.Cell>unfixed data</Table.Cell>
            </Table.Row>
          </Table.Body>
        </Table>
      </ScrollPane>
    </Box>
  </Story>
</Preview>

### With column horizontal scroll, sticky column, and batch actions (checkbox column)

This example shows how to include a sticky checkbox column to use when batch actions added to a table.

<Preview>
  <Story name="With Horizontal Scroll and Sticky Column and Spacer Removed">
    {() => {
      const useBatchSelect = (data) => {
        const [selectedItems, setSelectedItems] = React.useState(new Set());
        const clearAll = () => {
          setSelectedItems(new Set());
        };
        const selectAll = () => {
          if (data.length === selectedItems.size) {
            setSelectedItems(new Set());
          } else {
            setSelectedItems(new Set(data.map((d) => d.id)));
          }
        };
        const onSelect = (...args) => {
          const newSet = new Set(selectedItems);
          args.forEach((id) => {
            if (selectedItems.has(id)) {
              newSet.delete(id);
            } else {
              newSet.add(id);
            }
          });
          setSelectedItems(newSet);
        };
        return {
          selectedItems,
          clearAll,
          selectAll,
          onSelect,
        };
      };
      const data = "abcdefgh".split("").map((id) => ({ id }));
      const { selectedItems, onSelect, clearAll, selectAll } = useBatchSelect(
        data,
      );
      const totalItems = data.length;
      const Row = ({ id, selected }) => {
        return (
          <Table.Row selected={selected}>
            <Table.Cell>
              <Checkbox onClick={() => onSelect(id)} checked={selected} />
            </Table.Cell>
            <Table.Cell>I am Fixed!</Table.Cell>
            <Table.Cell>unfixed data</Table.Cell>
            <Table.Cell>unfixed data</Table.Cell>
            <Table.Cell>unfixed data</Table.Cell>
            <Table.Cell>unfixed data</Table.Cell>
            <Table.Cell>unfixed data</Table.Cell>
          </Table.Row>
        );
      };
      return (
        <Box className="h-46">
          <ScrollPane>
            <Table stickyColumn hasBatchActions affixHeader>
              <Table.Head>
                <Table.Row>
                  <Table.HeaderCell
                    sortable={false}
                    shrinkToContent
                    removeVerticalPadding
                  >
                    <Checkbox
                      onClick={() => {
                        if (selectedItems === totalItems) {
                          clearAll();
                        } else {
                          selectAll();
                        }
                      }}
                      checked={selectedItems.size === totalItems}
                    />
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #1 Fixed 400px
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #2
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #3
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #4
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #5
                  </Table.HeaderCell>
                  <Table.HeaderCell sortable={false} minColumnWidth="400px">
                    #6
                  </Table.HeaderCell>
                </Table.Row>
              </Table.Head>
              <Table.Body>
                {data.map((item) => {
                  return (
                    <Row
                      id={item.id}
                      key={item.id}
                      selected={selectedItems.has(item.id)}
                      onSelect={onSelect}
                    />
                  );
                })}
              </Table.Body>
            </Table>
          </ScrollPane>
        </Box>
      );
    }}
  </Story>
</Preview>

### With column widths

<Preview>
  <Story name="Table with column widths">
    <Table>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false} columnWidth="50%">
            50% Width
          </Table.HeaderCell>
          <Table.HeaderCell sortable={false} columnWidth="100px">
            100px Width
          </Table.HeaderCell>
          <Table.HeaderCell
            sortable={false}
            columnWidth="10%"
            minColumnWidth="300px"
          >
            300px Min-Width
          </Table.HeaderCell>
          <Table.HeaderCell sortable={false}>No Width Set</Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell>50%</Table.Cell>
          <Table.Cell>100px</Table.Cell>
          <Table.Cell>10% fixed width. 300px min-width</Table.Cell>
          <Table.Cell>none</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>

### With shrinking column

<Preview>
  <Story name="Table with shrinking column">
    <Table>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false} shrinkToContent>
            Shrink
          </Table.HeaderCell>
          <Table.HeaderCell sortable={false}>
            Don&apos;t Shrink
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell>
            I shrink as small as I can without breaking words
          </Table.Cell>
          <Table.Cell>I do not</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>

### With outer border

<Preview>
  <Story name="Table with border">
    <Table outerBorder>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false}>ID</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Name</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Email</Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell>1</Table.Cell>
          <Table.Cell>Wendy George</Table.Cell>
          <Table.Cell>wendy.george@healthco.com</Table.Cell>
        </Table.Row>
        <Table.Row>
          <Table.Cell>2</Table.Cell>
          <Table.Cell>Joe Smith</Table.Cell>
          <Table.Cell>joe.smith@healthco.com</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>

### With affixed header

For the table to scroll, it must be wrapped with a ScrollPane.

<Preview>
  <Story name="Table with affixed header">
    {() => {
      const rowData = [];
      for (let i = 0; i < 50; i += 1) {
        rowData.push({ number: i, evenOrOdd: i % 2 === 0 ? "even" : "odd" });
      }
      return (
        <Box className="h-46">
          <ScrollPane>
            <Table affixHeader>
              <Table.Head>
                <Table.Row>
                  <Table.HeaderCell sortable={false}>Number</Table.HeaderCell>
                  <Table.HeaderCell sortable={false}>
                    Even or Odd
                  </Table.HeaderCell>
                </Table.Row>
              </Table.Head>
              <Table.Body>
                {rowData.map((row) => {
                  return (
                    <Table.Row key={row.number}>
                      <Table.Cell>{row.number}</Table.Cell>
                      <Table.Cell>{row.evenOrOdd}</Table.Cell>
                    </Table.Row>
                  );
                })}
              </Table.Body>
            </Table>
          </ScrollPane>
        </Box>
      );
    }}
  </Story>
</Preview>

### With affixed header and sticky column

For the table to scroll, it must be wrapped with a ScrollPane.

<Preview>
  <Story name="Table with affixed header and sticky column">
    {() => {
      const rowData = [];
      for (let i = 0; i < 50; i += 1) {
        rowData.push({ number: i, evenOrOdd: i % 2 === 0 ? "even" : "odd" });
      }
      return (
        <Box className="h-46">
          <ScrollPane>
            <Table affixHeader stickyColumn>
              <Table.Head>
                <Table.Row>
                  <Table.HeaderCell minColumnWidth="200px" sortable={false}>
                    Number
                  </Table.HeaderCell>
                  <Table.HeaderCell minColumnWidth="200px" sortable={false}>
                    Even or Odd
                  </Table.HeaderCell>
                  <Table.HeaderCell minColumnWidth="800px" sortable={false}>
                    Lorem ipsum
                  </Table.HeaderCell>
                </Table.Row>
              </Table.Head>
              <Table.Body>
                {rowData.map((row) => {
                  return (
                    <Table.Row key={row.number}>
                      <Table.Cell>{row.number}</Table.Cell>
                      <Table.Cell>{row.evenOrOdd}</Table.Cell>
                      <Table.Cell>
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                        Cras ornare sem et ultricies scelerisque. Fusce
                        fermentum vestibulum pulvinar.
                      </Table.Cell>
                    </Table.Row>
                  );
                })}
              </Table.Body>
            </Table>
          </ScrollPane>
        </Box>
      );
    }}
  </Story>
</Preview>

### With no cell padding

<Preview>
  <Story name="Table with no cell padding">
    <Table>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false}>ID</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Name</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Email</Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell removePadding>No</Table.Cell>
          <Table.Cell removePadding>Padding</Table.Cell>
          <Table.Cell removePadding>Here</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>

### With no results

<Preview>
  <Story name="Table with no results">
    <Table noResults>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false}>ID</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Name</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Email</Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body />
    </Table>
  </Story>
</Preview>

### With loading cells

<Preview>
  <Story name="Table with loading cells">
    <Table>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false}>ID</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Name</Table.HeaderCell>
          <Table.HeaderCell sortable={false}>Email</Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell>1</Table.Cell>
          <Table.Cell isLoading>Wendy George</Table.Cell>
          <Table.Cell>wendy.george@healthco.com</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>

### With button added to `HeaderCell`

<Preview>
  <Story name="Table with HeaderCell button">
    <Table>
      <Table.Head>
        <Table.Row>
          <Table.HeaderCell sortable={false} colSpan={2} removeVerticalPadding>
            Metadata
            <Button
              icon={ICON_TYPE.PEN}
              variant={BUTTON_VARIANT.MINIMAL}
              className="my-1"
            >
              Edit
            </Button>
          </Table.HeaderCell>
        </Table.Row>
      </Table.Head>
      <Table.Body>
        <Table.Row>
          <Table.Cell>Name</Table.Cell>
          <Table.Cell>Wendy George</Table.Cell>
        </Table.Row>
      </Table.Body>
    </Table>
  </Story>
</Preview>
