# DataList

> **NOTICE:** DataList is a Legacy component and should not be used for new implementations.
> Use [DataTable](../DataTable/DataTable.md) composable atoms instead. Existing
> usages are supported.

DataList is used to show a collection list of similar items in a simpler manner
than [DataTable](../DataTable/DataTable.md).

It allows more control over the layout of the list items. It can also add
different layouts depending on the size of the screen that we support.

## Design & usage guidelines

While it's tempting to add a large amount of information to the DataList's
layout, it's important to keep in mind that the DataList is meant to be a simple
list of important items. We recommend not going over more than 5 data points per
item.

If you need to add more information, consider using the
[DataTable](../DataTable/DataTable.md) component instead.


## Configuration

Since DataList is a large component, we use a mixture of compound components and
props to separate each part of the component.

### Main wrapper

DataList is the main component that holds the composable parts of the list. It
also accepts props that are related to the list as a whole.

### Layout

DataList.Layout enables you to implement the layout of the list items. Keep in
mind that DataList doesn't have a default layout so you have to have at least 1
implemented.

It also supports different `size` breakpoints so you can completely change the
component it's using depending on the window size.

It is also important to add the type of `DataListItem<typeof data>` on the
`item` param in the layout so you can easily access the known data.

```tsx
// You can have 1 responsive layout
<DataList>
 <DataList.Layout>
   {/* Declare the type of the item */}
   {(item: DataListItem<MyDataType>) => (
     <Grid>
       <Grid.Cell size={{ xs: 12 md: 6 }}>{item.name}</Grid.Cell>
       <Grid.Cell size={{ xs: 12 md: 6 }}>{item.email}</Grid.Cell>
     </Grid>
   )}
 </DataList.Layout>
</DataList>


// Or you can have multiple layouts per breakpoint
<DataList>
 <DataList.Layout size="xs">
   {(item: DataListItem<MyDataType>) => (
     <Grid>
       <Grid.Cell size={{ md: 6 }}>{item.name}</Grid.Cell>
       <Grid.Cell size={{ md: 6 }}>{item.email}</Grid.Cell>
     </Grid>
   )}
 </DataList.Layout>


 <DataList.Layout size="md">
   {(item: DataListItem<MyDataType>) => (
     <Content>
       <div>{item.name}</div>
       <div>{item.email}</div>
     </Content>
   )}
 </DataList.Layout>
</DataList>
```

### Actions

#### Item actions

DataList.ItemActions allows you to add an action on the item itself such as
`onClick` or `url`. While the DataList.ItemAction (without an s) child adds more
action that is related to the item.

```tsx
<DataList>
  <DataList.ItemActions onClick={item => alert(item.name)}>
    <DataList.ItemAction icon="edit" label="Edit" onClick={} />
    <DataList.ItemAction icon="delete" label="Delete" onClick={} />
    <DataList.ItemAction
      label="Go to Jobber.com"
      actionUrl="https://www.jobber.com"
    />
  </DataList.ItemActions>
</DataList>
```

#### Hiding an action

Hide an action by using the `visible` prop. It accepts a function that has a
parameter of the item it's related to.

```tsx
<DataList.ItemAction
  visible={item => item.status === "Archived"}
  label="Delete"
/>
```

#### Batch actions

There are two steps to allow the user to select and perform actions on multiple
items:

1. Enable multiselect by adding a `selected`, `onSelect`, and `onSelectAll`
   props to the DataList component.
2. Add a DataList.BatchActions component inside the DataList.Layout component.

```tsx
<DataList
  selected={selected}
  onSelect={handleSelect}
  onSelectAll={handleSelectAll}
>
  <DataList.BatchActions>
    <DataList.BatchAction label="Send" onClick={handleSend} />
    <DataList.BatchAction label="Delete" onClick={handleDelete} />
  </DataList.BatchActions>
</DataList>
```

#### Using onSelectAll

There are two ways to use the `onSelectAll` prop:

Option 1. The "known IDs" way is where you update the selected IDs state with
all the possible IDs in an array format (`[1, 2, 3]`).

```tsx
return (
  <DataList
    selected={selected}
    onSelect={handleSelect}
    onSelectAll={handleSelectAll}
  >
    ...
  </DataList>
);

async function handleSelectAll() {
  const allIDs: number[] = await query.fetchIds();
  setSelected(allIDs);
}
```

Option 2. The "unknown IDs" way is where you pass in an object to the `selected`
prop. In doing so, the DataList will assume that everything is selected except
for the ID's in the `unselected` array.

```tsx
return (
  <DataList
    selected={selected}
    onSelect={handleSelect}
    onSelectAll={handleSelectAll}
  >
    ...
  </DataList>
);

/**
 * In this example, `selected` will be an object with the following shape:
 * {
 *   totalCount: boolean; // approximately how many items are selected
 *   unselected: T[]; // the items that are not selected
 * }
 */
function handleSelectAll(selected: DataListSelectAllData<T>) {
  setSelected(selected);
}
```

#### Layout actions

By default, the actions from the DataList.ItemActions will be placed on the top
right of the list item and will be triggered by hover. You can change this
behavior by including the DataList.LayoutActions component inside the
DataList.Layout component.

```tsx
<DataList>
 <DataList.Layout>
   {(item: DataListItem<MyDataType>) => (
     <>
       <Grid>
         <Grid.Cell size={{ xs: 12 md: 6 }}>{item.name}</Grid.Cell>
         <Grid.Cell size={{ xs: 12 md: 6 }}>{item.email}</Grid.Cell>
       </Grid>


       {/* This will place the actions as a part of the layout */}
       <DataList.LayoutActions />
     </>
   )}
 </DataList.Layout>
</DataList>
```

### Filters

DataList.Filters adds filters to the list. This compound component accepts any
children and inserts it above the list of items.

```tsx
<DataList>
  <DataList.Filters>
    <FilterPicker />
    <Button />
    <Select />
  </DataList.Filters>
</DataList>
```

#### Clearing all filters

##### In all filter groups (e.g. with the "Clear Filters" button)

You can add a button that will clear all the filters by using the combination of
the `FilterPicker` and `Button` components (check out the `Code` tab in the
["Clear All Filters" story](/storybook/web/?path=/story/components-lists-and-tables-datalist--clear-all-filters)
for a more elaborate example).

```tsx
const [groupOneSelectedFilters, setGroupOneSelectedFilters] = useState([]);
const [groupTwoSelectedFilters, setGroupTwoSelectedFilters] = useState([]);

function removeAllFilters() {
  setGroupOneSelectedFilters([]);
  setGroupTwoSelectedFilters([]);
}

<DataList.Filters>
  <FilterPicker
    label="Filter Group 1"
    selected={groupOneSelectedFilters}
    onSelect={handleSelectGroupOneFilters}
    multiSelect
  >
    {groupOneSelectedFilters.map(filter => (
      <FilterPicker.Option key={filter} id={filter} label={filter} />
    ))}
  </FilterPicker>

  <FilterPicker
    label="Filter Group 2"
    selected={groupTwoSelectedFilters}
    multiSelect
    onSelect={handleSelectGroupOneFilters}
  >
    {groupTwoSelectedFilters.map(filter => (
      <FilterPicker.Option key={filter} id={filter} label={filter} />
    ))}
  </FilterPicker>

  <Button
    label="Clear filters"
    type="tertiary"
    variation="subtle"
    onClick={removeAllFilters}
  />
</DataList.Filters>;
```

##### Within one filter group (e.g. Suffix on the Chip)

If you'd like to to implement a filter mechanism to clear all filters within one
group without opening the `FilterPicker` "dropdown", you could achieve this by
using `<FilterPicker.Activator>` and `<Chip>` components (check out the `Code`
tab in the
["Clear All Filters" story](/storybook/web/?path=/story/components-lists-and-tables-datalist--clear-all-filters))
for a more elaborate example).

NOTE: You will need to add conditional logic in several places based on whether
or not anything was selected in a filter group to provide UI and UX similar to a
standard `DataList` with filters.

```tsx
const [selectedFilters, setSelectedFilters] = useState([]);

<FilterPicker
  label=""
  selected={selectedFilters}
  onSelect={setSelectedFilters}
  multiSelect
>
  <FilterPicker.Activator>
    <Chip
      label={
        selectedFilters.length
          ? // this will display the selected filters in the Chip
            selectedFilters.map(({ label }) => label).join(", ")
          : ""
      }
      heading="Your label"
      // this will change the Chip's appearance based on whether or not
      // any filters are selected
      variation={selectedFilters.length ? "base" : "subtle"}
    >
      <Chip.Suffix
        // this will conditionaly add an `onClick` prop (to clear all filters
        // within the group)
        {...(selectedFilters.length
          ? {
              onClick: () =>
                handleRemoveIndividualFilterGroup(key as keyof SelectedFilters),
            }
          : {})}
      >
        <Icon
          // this will show an "X" or "+" icon based on whether or not any
          // filters are selected within the group
          name={selectedFilters.length ? "cross" : "add"}
          size="small"
        />
      </Chip.Suffix>
    </Chip>
  </FilterPicker.Activator>

  {filterOptions.map((option: string) => (
    <FilterPicker.Option key={option} id={option} label={option} />
  ))}
</FilterPicker>;
```

You can also combine both "Clear filter" methods (within one group as well as to
clear filters in ALL groups) to achieve a desired UX.

### Search

DataList.Search adds a search input to the list. This is debounced by 500ms.

```tsx
<DataList>
  <DataList.Search onSearch={handleSearch} />
</DataList>
```

#### Controlling the search input

By default, DataList.Search is uncontrolled and internally manages its value. If
you need the ability to control it, you can pass in a `value` prop and use the
`onSearch` prop to handle updating your state with the latest value. In this
case `onSearch` will *not* be debounced. Be sure to gracefully handle rapid key
presses and avoid over-triggering expensive operations, like network requests
for example.

```tsx
const [searchValue, setSearchValue] = useState("");

const debouncedRequest = useDebounce((search: string) => {
  console.log("delayed request", search);
}, 300);

<DataList>
  <DataList.Search
    value={searchValue}
    onSearch={search => {
      setSearchValue(search);
      debouncedRequest(search);
    }}
  />
</DataList>;
```

### Empty state

The DataList has a default empty state message when it is empty or filtering.
You can override this by using the DataList.EmptyState component.

```tsx
<DataList>
  <DataList.EmptyState
    message="Character list is looking empty"
    action={
      <Button
        label="New character"
        onClick={() => alert("Create a new character")}
      />
    }
  />
</DataList>
```

You can also add a custom empty message when you've filtered the list and there
are no results. Please note that for this specific empty state to show up, you
need to set the `filtered` to `true`.

```tsx
<DataList filtered={true}>
  <DataList.EmptyState
    type="filtered"
    message="No results found"
    action={
      <Button label="Clear filters" onClick={() => alert("Clear filters")} />
    }
  />
</DataList>
```

#### Custom empty state

If you need advanced customization for the empty state, you can use the
`customRender` prop to supply your own content. See the
[Custom Render Empty State](/storybook/web/?path=/story/components-lists-and-tables-datalist--custom-render-empty-state)
story for an example.

```tsx
<DataList>
 <DataList.EmptyState
   type="empty"
   message="No results found"
   customRender={({ message }) => (
     <div>
       <h3>{message}</h3>
       <Flex template={["grow", "shrink"]} direction="column">
         <Button label="Create a new character" onClick={() => alert("Create")} />
         <Button
           label="Clear filters"
           type="secondary"
           onClick={() => alert("Clear filters")}
         />
       </Flex>
     </div>
   )}
 />
<DataList>
```

### Status bar

You can add a status bar that will be displayed between the filters and the
header. This status bar can be used to display error messages or anything you
want to inform the user about something related to the Datalist.

```tsx
<DataList>
  <DataList.StatusBar>
    <Banner type="error" icon="alert" dismissible>
      Something went wrong. Refresh or check your internet connection.
    </Banner>
  </DataList.StatusBar>
</DataList>
```

### Sorting

The `sorting` prop is an object that configures the sorting functionality of the
DataList.

The `state` property is an object that holds the current sorting state of the
DataList. The `onSort` property is a function that is called when the user
clicks on a sortable column. Lastly, `sortable` is an array that holds
information about which columns are sortable.

Each column is identified by a `key` and will have a `sortType` property that
can be either `dropdown` or `toggle`. The `options` property is an array of
objects that will define the sorting options for the column.

```tsx
<DataList
 sorting={{
   state: sortingState,
   onSort: sorting => {
     setSortingState(sorting);
   },
   sortable: [
     {
       key: "label",
       sortType: "dropdown",
       options: [
         {
           id: "firstName",
           label: "First name (A-Z)",
           order: "asc",
         },
         {
           id: "firstName",
           label: "First name (Z-A)",
           order: "desc",
         },
         { id: "lastName",
           label: "Last name (A-Z)",
           order: "asc"
         },
         {
           id: "lastName",
           label: "Last name (Z-A)",
           order: "desc",
         },
       ],
     },
   ],
```


## Props

### Web

#### DataList

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `data` | `T[]` | Yes | — | The data to render in the DataList. |
| `headers` | `DataListHeader<T>` | Yes | — | The header of the DataList. The object keys are determined by the keys in the data. |
| `filtered` | `boolean` | No | — | Adjusts the DataList to show the UX when it is filtered. |
| `headerVisibility` | `{ xs?: boolean; sm?: boolean; md?: boolean; lg?: boolean; xl?: boolean; }` | No | `{ xs: true, sm: true, md: true, lg: true, xl: true }` | Determine if the header is visible at a given breakpoint. If one isn't provided, it will use the value from the next ... |
| `loadingState` | `"filtering" | "initial" | "loadingMore" | "none"` | No | — | Set the loading state of the DataList. There are a few guidelines on when to use what.  - `"initial"` - loading the f... |
| `onLoadMore` | `() => void` | No | — | The callback function when the user scrolls to the bottom of the list. |
| `onSelect` | `(selected: DataListSelectedType<T["id"]>) => void` | No | — | Callback when an item checkbox is clicked. |
| `onSelectAll` | `(selected: DataListSelectedType<T["id"]>) => void` | No | — | Callback when the select all checkbox is clicked. |
| `selected` | `DataListSelectedType<T["id"]>` | No | `[]` | The list of Selected Item ids |
| `sorting` | `{ readonly sortable: DataListSortable[]; readonly state: DataListSorting; readonly onSort: (sorting?: DataListSorting) => void; }` | No | — | `sortable`: List of keys that are sortable. `state`: The state of the sorting. `onSort`: The callback function when t... |
| `title` | `string` | No | — | The title of the DataList. |
| `totalCount` | `number` | No | — | Total number of items in the DataList.  This renders an "N results" text with the DataList that helps users know how ... |

#### DataList.BatchAction

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `label` | `string | ((item: DataListObject) => string)` | Yes | — | The label of the action |
| `actionUrl` | `string` | No | — | The URL to navigate to when the action is clicked. |
| `alwaysVisible` | `boolean` | No | — | Determine if the action is always visible. It is not recommended to set this to true on more then one action. |
| `destructive` | `boolean` | No | — | Adjust the styling of an action label and icon to be more destructive. |
| `icon` | `IconNames` | No | — | The icon beside the label |
| `onClick` | `() => void` | No | — | The callback function when the action is clicked. |

#### DataList.BatchActions

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `Fragment<ReactElement<DataListBulkActionProps, string | JSXElementConstructor<any>>>` | No | — | The actions to render on the top of the DataList to make actions to multiple items. This only accepts the DataList.Ba... |

#### DataList.EmptyState

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `message` | `string` | Yes | — | The message that shows when the DataList is empty. |
| `action` | `ReactElement<{ onClick?: never; external?: never; readonly name?: string; submit: never; readonly type?: ButtonType; readonly value?: string; readonly disabled?: boolean; readonly loading?: boolean; ... 17 more ...; readonly children?: never; } | ... 34 more ... | { ...; }, string | JSXElementConstructor<...>>` | No | — | The action that shows when the DataList is empty.  This only accepts a Button component. Adding a non-Button componen... |
| `customRender` | `(emptyState: Omit<DataListEmptyStateProps, "customRender">) => ReactNode` | No | — | Custom render function for the empty state.  If provided, this function will be used to render the empty state instea... |
| `type` | `"empty" | "filtered"` | No | — | Determine the type of empty state to show.  By default, it will show the "empty" state when there is no data. If you ... |

#### DataList.ItemActions

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `Fragment<ReactElement<DataListActionProps<T>, string | JSXElementConstructor<any>>>` | No | — | The actions to render for each item in the DataList. This only accepts the DataList.Action component. |
| `disableContextMenu` | `boolean` | No | `false` | Disable the custom context menu. This allows the browser's native context menu to be shown. |
| `onClick` | `(item: T, event?: MouseEvent<HTMLElement, MouseEvent>) => void` | No | — | Callback when an item is clicked. |
| `to` | `string | ((item: T) => string)` | No | — | If a React Navigation is needed, use this prop to use the `Link` component that comes with React Router. |
| `url` | `string | ((item: T) => string)` | No | — | If a normal page navigation is needed, use this prop to change the element to an `a` tag with an `href`. |

#### DataList.Search

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `onSearch` | `(value: string) => void` | Yes | — |  |
| `initialValue` | `string` | No | — | The initial value of the search input.  Updating this prop after the component has mounted will rerender the componen... |
| `placeholder` | `string` | No | — | The placeholder text for the search input. This either uses the title prop prepended by "Search" or just falls back t... |
| `value` | `string` | No | — | The controlled value of the search input.  Supply this field if you want to take control over the search input's valu... |
