Skip to content
MorganeLecurieux edited this page May 18, 2022 · 1 revision


In the app, all the full page lists have the same features:

  • Infinite scroll
  • Key board navigation (K / J / ⬆️ / ⬇️)

Design

To respect the design conventions, you need to use the elements found in the list style file.

  • ListHeader - container for the header
  • ListContainer - container for the list items
  • BaseListItem - used for skeletons only
  • ListItem - used for any list item
  • ItemContainer - used only in case there is a dropdown of action in the item
  • PopperOpener - used for the dropdown button opener to position it correctly

Clickable item with dropdown menu



Items usually redirects to another page (edition/details). To avoid to trigger the redirection while opening the dropwdown, you need to use the following code :

import { ListItem, PopperOpener, ItemContainer } from "~/styles";

const Item = () => {
  return (
    <ItemContainer>
      <ListItem
        onClick={() => {
          /* TODO */
        }}
      >
        {/* Content of the item */}
        <ButtonMock />
      </ListItem>
      <Popper
        opener={
          <PopperOpener>
            <Button icon="dots-horizontal" variant="quaternary" />
          </PopperOpener>
        }
      >
        {/* Popper content */}
      </Popper>
    </ItemContainer>
  );
};

// Placeholder for the popper button that is in aboslute position on top of the item
const ButtonMock = styled.div`
  width: 40px;
`;

Infinite scroll

The infinite scroll is used with a paginated query. Just wrap your list with the InfiniteScroll component as follow :

import { InfiniteScroll } from "~/components/designSystem";

const List = () => {
  const { data, error, loading, fetchMore } = useCustomersQuery({
    variables: { limit: 20 },
    notifyOnNetworkStatusChange: true, // This is needed for the "loading" to be set to "true" while fetching more
  });

  return (
    <InfiniteScroll
      onBottom={() => {
        const { currentPage = 0, totalPages = 0 } = data?.items?.metadata || {};

        currentPage < totalPages &&
          !loading &&
          fetchMore({
            variables: { page: currentPage + 1 },
          });
      }}
    >
      <>
        {!!list &&
          list.map((item) => {
            index += 1;

            return <Item key={item.id} item={item} />;
          })}
        {loading &&
          [0, 1, 2].map((i) => <Skeletong key={`item-skeleton-${i}`} />)}
      </>
    </InfiniteScroll>
  );
};

Key board navigation

Use the useListKeysNavigation hook. It will deal with the navigation automatically.

Then in your component :

const List = () => {
      const { onKeyDown } = useListKeysNavigation({
    getElmId: (i) => `item-key-${i}`, // identifier used to get each element
  })
  const list = [] // Elements fetched

    return (
        <div role="grid" tabIndex={-1} onKeyDown={onKeyDown}>
             {/* List header, empty state, error state... */}
             {!!list &&
                list.map((item) => {
                  index += 1

                  return (
                    <CustomerItem
                      key={customer.id}
                      rowId={`item-key-${index}`} // This must be the same structure as the identifier given to useListKeysNavigation
                      item={item}
                    />
                  )
                })}
        <div>
    )
}

Do not forget then to pass rowId as the id to the Item so it can be focused onKeyDown. The tabIndex is necessary to allow the item to be focused.

const Item = ({ rowId }) => {
    return (
        <ItemContainer>
            <Item
                id={rowId}
                tabIndex={0}
                onClick={() => {}}
            >
                {/* Item content */}
        </ItemContainer>
      </Item>
    )
}
Clone this wiki locally