import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { IntlProvider } from 'react-intl';
import FlexTable from './FlexTable';
import { Number } from '../..';

<Meta title="Molecules | FlexTable" component={FlexTable} />

# FlexTable

Flextable is a table component built using the `Flexbox` component to allow responsive headers and columns.
The Flextable component only handles UI states.

<Canvas>
  <FlexTable>
    <FlexTable.HeaderRow>
      <FlexTable.Header columnId="instrument-name">Instrument Name</FlexTable.Header>
      <FlexTable.Header columnId="currency">Currency</FlexTable.Header>
      <FlexTable.Header columnId="country">Country</FlexTable.Header>
    </FlexTable.HeaderRow>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Ericsson</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">SEK</FlexTable.Cell>
      <FlexTable.Cell columnId="country">SE</FlexTable.Cell>
    </FlexTable.Row>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Apple</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">USD</FlexTable.Cell>
      <FlexTable.Cell columnId="country">US</FlexTable.Cell>
    </FlexTable.Row>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Nokia</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">EUR</FlexTable.Cell>
      <FlexTable.Cell columnId="country">FI</FlexTable.Cell>
    </FlexTable.Row>
  </FlexTable>
</Canvas>

**Table of Contents**

- Customization
- FlexTable component
- Table Title
- Header
  - Sorting
  - Custom Header
- Cell
  - Custom Cell
- Footer
- Row
  - Header Row & Footer Row
- Constants
- Design Notes
- Performance

## Customization

To enable users to customise their components, `FlexTable` and its components are developed to support plain strings, components and functions as children.
Sending in a plain string as a child will wrap the string with content wrappers to set e.g. the correct font, font size and padding.
If the user sends in a component it will be rendered without content wrappers, enabling full customizability. To simplify reuse of the table standards,
the components export all their content wrappers as building stones, to create your own custom component. See the example below

```jsx
<FlexTable.Cell>String</FlexTable.Cell>

// is equivalent to writing

<FlexTable.Cell>
  <FlexTable.Cell.TextWrapper>
    String
  </FlexTable.Cell.TextWrapper>
</FlexTable.Cell>
```

In this example `Cell` exports its building block `TextWrapper`. `TextWrapper` includes the font size and truncation used when supplying a plain string as a child.
This allows building custom components that fulfill the FlexTable standards, see below

```jsx
const FlagNumberCell = React.memo(({ value, country, currency }) => (
  <FlexTable.Cell>
    <FlexTable.Cell.TextWrapper>
      <Flag country={country} />
      <Number value={value} currency={currency} />
    </FlexTable.Cell.TextWrapper>
  </FlexTable.Cell>
));

<FlagNumberCell value={23} country="SE" currency="SEK" />;
```

This example cell will fit in with the rest of the cells and change its font size depending on what is set on the `FlexTable`.

## FlexTable component

`xs`, `sm`, `md`, `lg` and `xl` are all applicable to `FlexTable` as with `Flexbox` enabling users to modify properties depending on screen size.

<Canvas>
  <FlexTable density="l" fontSize="m" md={{ density: 's', fontSize: 's' }}>
    <FlexTable.HeaderRow>
      <FlexTable.Header columnId="instrument-name">Instrument Name</FlexTable.Header>
      <FlexTable.Header columnId="currency">Currency</FlexTable.Header>
      <FlexTable.Header columnId="country">Country</FlexTable.Header>
    </FlexTable.HeaderRow>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Ericsson</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">SEK</FlexTable.Cell>
      <FlexTable.Cell columnId="country">SE</FlexTable.Cell>
    </FlexTable.Row>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Apple</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">USD</FlexTable.Cell>
      <FlexTable.Cell columnId="country">US</FlexTable.Cell>
    </FlexTable.Row>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Nokia</FlexTable.Cell>
      <FlexTable.Cell columnId="currency">EUR</FlexTable.Cell>
      <FlexTable.Cell columnId="country">FI</FlexTable.Cell>
    </FlexTable.Row>
  </FlexTable>
</Canvas>

## Layout properties

The layout of the table can be customized using the same properties as `FlexBox` uses.
For performance reason as well as to make the table compatible with server-side rendering (SSR), the layout properties have to be attached to each of the `Header`, `Cell` and `Footer` components with the same `columnId`.
If no layout properties are specified, some default values will be used.

**NOTE: Previously the layout properties were only attached to the `Header` component, and that would set the layout for the entire column. This API was designed with ease-of-use in mind, but unfortunately it creates
some performance problems with cells being rendered multiple times and it does not work at all in an SSR context. This API has therefore been deprecated and layout properties should now be added to each of the cells in a column instead.**

## Table Title

Title for the the table can be a string or a react component.
If a title is set for the table, an id must be set as well. The id will be set as as the id for the whole table, but also used as a base for the id of the table title.

\*\*NOTE: For some reason if Flextable is wrapped as a styled component the TypeScript validation for the id and title props breaks. This is important to know because if there is a title prop then there must be an id for a11y reasons.

## Header

`Header` uses layout properties from props and handles sorting.
The `fontSize` defined in the `FlexTable` component is inherited here. If the content does not fit within the `Header`, it will be truncated and produces a tooltip on hover via the molecule `TruncateWithToolTip`.

<Canvas>
  <FlexTable>
    <FlexTable.HeaderRow>
      <FlexTable.Header columnId="instrument-name">Instrument Name (default)</FlexTable.Header>
      <FlexTable.Header columnId="currency" flex="0 0 150px">
        Currency (150px)
      </FlexTable.Header>
      <FlexTable.Header columnId="country" justifyContent="flex-end" flex="2">
        Country (flex 2, justify end)
      </FlexTable.Header>
    </FlexTable.HeaderRow>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Ericsson</FlexTable.Cell>
      <FlexTable.Cell columnId="currency" flex="0 0 150px">
        SEK
      </FlexTable.Cell>
      <FlexTable.Cell columnId="country" justifyContent="flex-end" flex="2">
        SE
      </FlexTable.Cell>
    </FlexTable.Row>
  </FlexTable>
</Canvas>

`Header` features conditional hiding based on `Media`. To hide columns e.g. if size `md` or smaller:

<Canvas>
  <FlexTable>
    <FlexTable.HeaderRow>
      <FlexTable.Header columnId="instrument-name">Instrument Name</FlexTable.Header>
      <FlexTable.Header columnId="currency" hidden md={{ hidden: false }}>
        Currency (hidden on mobile)
      </FlexTable.Header>
      <FlexTable.Header columnId="country" md={{ justifyContent: 'flex-end' }}>
        Country (justify end on desktop)
      </FlexTable.Header>
    </FlexTable.HeaderRow>
    <FlexTable.Row>
      <FlexTable.Cell columnId="instrument-name">Ericsson</FlexTable.Cell>
      <FlexTable.Cell columnId="currency" hidden md={{ hidden: false }}>
        SEK
      </FlexTable.Cell>
      <FlexTable.Cell columnId="country" md={{ justifyContent: 'flex-end' }}>
        SE
      </FlexTable.Cell>
    </FlexTable.Row>
  </FlexTable>
</Canvas>

### Sorting

To enable sorting, the `sortable` property has to be set to true. The user can have either a controlled or uncontrolled sorting state by setting the `sortOrder` property.
In an uncontrolled state the header will unsort the other headers when a header is sorted. The default sorting rotation is _unsorted --> ascending --> descending --> unsorted_.

Note: To ease sorting, we recommend that the `columnId` for each column has the same name as the data property that the column represents.
In the example below, The `columnId` of the first column represents `instrumentName` field in the data, `columnId` of second column represents `currency` etc.

```jsx
const data = [
  {instrumentName: "Ericsson", currency: "SEK", country: "SE"},
  {instrumentName: "Apple", currency: "USD", country: "US"},
  {instrumentName: "Nokia", currency: "EUR", country: "FI"}
];

const InstrumentTable = () => (
  const [sorting, setSorting] = useState({columnId: "instrumentName", sortOrder: FlexTable.CONSTANTS.SORT_ORDER_NONE});
  const sortedData = useMemo(() => {
    if(sorting.sortOrder === FlexTable.CONSTANTS.SORT_ORDER_NONE){
      return data;
    }

    // Array sort mutates the original array, therefore you should always make a copy
    return data.slice(0).sort(
      (instrumentA,instrumentB ) =>
        sorting.sortOrder === FlexTable.CONSTANTS.SORT_ORDER_ASCENDING
        ? instrumentA[sorting.columnId].toLocaleCompare(instrumentB[sorting.columnId])
        : instrumentB[sorting.columnId].toLocaleCompare(instrumentA[sorting.columnId]);
      );
  }, [sorting, data]);

  return (
    <FlexTable>
      <FlexTable.HeaderRow>
        <FlexTable.Header columnId="instrumentName" sortable>Instrument Name</FlexTable.Header>
        <FlexTable.Header columnId="currency" sortable>Currency</FlexTable.Header>
        <FlexTable.Header columnId="country" sortable>Country</FlexTable.Header>
      </FlexTable.HeaderRow>
      {sortedData?.map(({instrumentName, currency, country}) => (
        <FlexTable.Row>
          <FlexTable.Cell columnId="instrumentName">{instrumentName}</FlexTable.Cell>
          <FlexTable.Cell columnId="currency">{currency}</FlexTable.Cell>
          <FlexTable.Cell columnId="country">{country}</FlexTable.Cell>
        </FlexTable.Row>
      ))}
    </FlexTable>
  )
);
```

### Custom Header

`Header` contains sorting, to customise a sortable table header you should use `Flexbox` to isolate the different items

```jsx
const CustomisedTableHeader = React.memo(({ flag, country }) => (
  <FlexTable.Header columnId="CountryAndCurrency" sortable>
    {({ sortable, sorted, onSortClick, sortOrder }) => (
      <FlexTable.Header.SortButton onClick={onSortClick}>
        <Flexbox container>
          <Flexbox item>
            <Flag country={flag} inline height={3} />
          </Flexbox>
          <FlexTable.CellInlineContainer item>
            <FlexTable.Header.TextWrapper sorted={sorted}>{country}</FlexTable.Header.TextWrapper>
          </FlexTable.CellInlineContainer>
          {sortable && (
            <Flexbox item>
              <FlexTable.Header.SortIcon sortOrder={sortOrder} />
            </Flexbox>
          )}
        </Flexbox>
      </FlexTable.Header.SortButton>
    )}
  </FlexTable.Header>
));
```

`CellInlineContainer` is a Flexbox-wrapper that makes a `Flexbox` item able to truncate, also makes sure everything inside has correct vertical alignment due to tooltip.
`<Flexbox container>` may need to be styled with css to inherit flexbox values. For example

```jsx

const StyledFlexboxContainer = styled(Flexbox)
  justify-content: inherit;
  align-items: inherit;
;

<StyledFlexboxContainer container>
  ...
</StyledFlexboxContainer>

```

## Cell

Cell uses the layout properties from props.

### Custom Cell

You can pass any valid React Element as children to Cell.
In the example below we are using Cell's TextWrapper component, which automatically gets the fontSize from the FlexTable component.

```jsx
const NumberCell: React.FC<{ columnId: string }> = React.memo(({ value, columnId }) => (
  <FlexTable.Cell columnId={columnId}>
    <FlexTable.Cell.TextWrapper>
      <Number value={value} />
    </FlexTable.Cell.TextWrapper>
  </FlexTable.Cell>
));
```

Note the use of `React.memo`, this is important to include to improve sorting speed.
More info regarding this can be found under _Performance_.

## Footer

Similar functionality to `Cell`.

<Canvas>
  <IntlProvider locale="en-UK">
    <FlexTable>
      <FlexTable.HeaderRow>
        <FlexTable.Header columnId="instrument-name">Instrument Name</FlexTable.Header>
        <FlexTable.Header columnId="amt" hidden md={{ hidden: false }} justifyContent="flex-end">
          Amount
        </FlexTable.Header>
        <FlexTable.Header columnId="percentage" justifyContent="flex-end">
          Percentage
        </FlexTable.Header>
      </FlexTable.HeaderRow>
      <FlexTable.Row>
        <FlexTable.Cell columnId="instrument-name">Ericsson</FlexTable.Cell>
        <FlexTable.Cell columnId="amt" hidden md={{ hidden: false }} justifyContent="flex-end">
          <FlexTable.Cell.TextWrapper>
            <Number value={100} />
          </FlexTable.Cell.TextWrapper>
        </FlexTable.Cell>
        <FlexTable.Cell columnId="percentage" justifyContent="flex-end">
          <FlexTable.Cell.TextWrapper>
            <Number value={25} percentage />
          </FlexTable.Cell.TextWrapper>
        </FlexTable.Cell>
      </FlexTable.Row>
      <FlexTable.Row>
        <FlexTable.Cell columnId="instrument-name">Apple</FlexTable.Cell>
        <FlexTable.Cell columnId="amt" hidden md={{ hidden: false }} justifyContent="flex-end">
          <FlexTable.Cell.TextWrapper>
            <Number value={300} />
          </FlexTable.Cell.TextWrapper>
        </FlexTable.Cell>
        <FlexTable.Cell columnId="percentage" justifyContent="flex-end">
          <FlexTable.Cell.TextWrapper>
            <Number value={75} percentage />
          </FlexTable.Cell.TextWrapper>
        </FlexTable.Cell>
      </FlexTable.Row>
      <FlexTable.FooterRow>
        <FlexTable.Footer columnId="instrument-name">Summary</FlexTable.Footer>
        <FlexTable.Footer columnId="amt" hidden md={{ hidden: false }} justifyContent="flex-end">
          <FlexTable.Footer.TextWrapper>
            <Number value={400} />
          </FlexTable.Footer.TextWrapper>
        </FlexTable.Footer>
        <FlexTable.Footer columnId="percentage" justifyContent="flex-end">
          <FlexTable.Footer.TextWrapper>
            <Number value={100} percentage />
          </FlexTable.Footer.TextWrapper>
        </FlexTable.Footer>
      </FlexTable.FooterRow>
    </FlexTable>
  </IntlProvider>
</Canvas>

## Row

Row has the important feature of managing expand states.
For it to know how to handle an out-of-the-box-expand it needs `expandItems` and/or `expandChildren`, alongside the parent `FlexTable` having `expandable = true`.
If neither `expandItems` or `expandChildren` are supplied, the chevron will be disabled.

Conditionally hiding `expandItems` for different screen sizes can be implemented in the following way:

```jsx
const ConditionalMediaExpand = () => {
  const columnData = [
    { label: 'Country', value: 'SE' }, // visible in all screen sizes having `expandable = true`
    { label: 'Currency', value: 'SEK', sm: { hidden: true } }, // this expand item will be hidden on sm devices (and up)
  ];
  return (
    <FlexTable expandable md={{ expandable: false }}>
      <FlexTable.HeaderRow>
        <FlexTable.Header columnId="instrumentName">Instrument Name</FlexTable.Header>
        <FlexTable.Header columnId="country" hidden md={{ hidden: false }}>
          Country
        </FlexTable.Header>
        <FlexTable.Header columnId="currency" hidden sm={{ hidden: false }}>
          Currency
        </FlexTable.Header>
      </FlexTable.HeaderRow>

      <FlexTable.Row expandItems={columnData}>
        <FlexTable.Cell columnId="instrumentName">Ericsson</FlexTable.Cell>
        <FlexTable.Cell columnId="country" hidden md={{ hidden: false }}>
          SE
        </FlexTable.Cell>
        <FlexTable.Cell columnId="currency" hidden sm={{ hidden: false }}>
          SEK
        </FlexTable.Cell>
      </FlexTable.Row>
    </FlexTable>
  );
};
```

Several Media breakpoints can be used inside `expandItems`.

### Header Row & Footer Row

Header Row and Footer Row has the same functionality as an ordinary `Row` but with other default properties to fulfill their purpose.
Most notably, the property `rowType` is set to either `'header'` or `'footer'`, instead of the default value `'content'`.
This keeps the row from rendering a chevron and instead provides a empty `Header` with fixed properties with the same `columnId` as all the chevrons in the table.

## Constants

`FlexTable` exports constants that can be used for default handling. The constants are available under `FlexTable.CONSTANTS`. These are:

1. `SORT_ORDER_NONE` / `SORT_ORDER_ASCENDING` / `SORT_ORDER_DESCENDING`

   To handle the correct `string` for sort orders. Are `'none'`, `'ascending'` and `'descending'` respectively.

1. `COLUMN_ID_EXPAND = 'column-expand'`

   The `id` that is assigned to `HeaderRow`'s empty `Header` and `Row`'s chevron.

1. `ICON_COLUMN_DEFAULT_FLEX_PROPS`

   The layout properties used on the Icon column.

## Design Notes

These pertain to the design standards of the FlexTable

- In general, columns with cells that use number values should be right aligned, i.e. `justifyContent="flex-end"`
- When `density` is `s`, `fontSize` value should be `s`. For other `density` values `fontSize` could be either `s` or `m`
- If the column header ends with a currency, the currency shouldn't truncate. An example of how to truncate the rest can be found in the stories

## Performance

The table should be able to handle large numbers of rows without a hitch. However sorting all the rows creates a lot of unnecessary re-renders. Each row is re-rendered to its new position
and also each cell. That means for a 50 row table with 10 columns, we will have at least 500 components re-rendering. For this reason we have used `React.memo` on all components to
smoothen data operations that does not change the table content, only the positioning such as when sorting.

- Use `React.memo` on all custom `Row`, `Cell`, `Header` and `Footer` implementations.
- When sending in media objects, make sure that the objects are memoized. This will stop unnecesary re-renders of rows.
- If you have more than 100 rows, it's worth looking into virtualization of rows. An example can be found in the `FlexTable` stories.
