Skip to content
Sancho UI
Toggle dark mode
Getting started
Built with ☕ by
Ben McMahen

List

Lists are useful for rendering collections of documents or references, like emails, actions, documents, and so on.

Sancho exports the following components to create lists:

  • List: The wrapper component for ListItem and ListSection children.
  • ListSection: A labelled wrapper component for ListItem components.
  • ListItem: The list item itself.

Basic usage

/** @jsx jsx */

function Example() {
  return (
    <Layer css={{ overflow: "hidden", maxWidth: "100%", width: "450px" }}>
      <List>
        <ListItem
          contentBefore={
            <Avatar name={"Ben McMahen"} src={faker.image.avatar()} />
          }
          primary="Ben McMahen"
          secondary="Minim do minim cupidatat veniam aliquip sunt exercitation enim nisi nulla."
          contentAfter={<IconChevronRight />}
        />
        <ListItem
          contentBefore={
            <Avatar name={"Ben McMahen"} src={faker.image.avatar()} />
          }
          primary="Joe Chen"
          secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
          contentAfter={<IconChevronRight />}
        />
        <ListItem
          contentBefore={
            <Avatar name={"Lynn Apple"} src={faker.image.avatar()} />
          }
          primary="Lynn Apple"
          secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
          contentAfter={<IconChevronRight />}
        />
        <ListItem
          contentBefore={
            <Avatar name={"Mary Joe"} src={faker.image.avatar()} />
          }
          primary="Mary Joe"
          secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
          contentAfter={<IconChevronRight />}
        />
      </List>
    </Layer>
  );
}
Messenger
/** @jsx jsx */

function Example() {
  const [index, setIndex] = React.useState(0);

  return (
    <Layer className="List-example">
      <Toolbar className="List-toolbar">
        <Text gutter={false} variant="h6">
          Messenger
        </Text>
      </Toolbar>

      <Tabs
        className="List-tabs"
        variant="evenly-spaced"
        onChange={i => setIndex(i)}
        value={index}
      >
        <Tab id="family">Family</Tab>
        <Tab id="work">Work</Tab>
        <Tab id="favorites">Favorites</Tab>
        <Tab id="groups">Groups</Tab>
      </Tabs>

      <Pager value={index} onRequestChange={i => setIndex(i)}>
        <TabPanel id="family">
          <List>
            <ListItem
              contentBefore={
                <Avatar name={"Lynn Apple"} src={faker.image.avatar()} />
              }
              primary="Lynn Apple"
              wrap={false}
              secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
              contentAfter={<Badge>1</Badge>}
            />
            <ListItem
              contentBefore={
                <Avatar name={"Mary Joe"} src={faker.image.avatar()} />
              }
              primary="Mary Joe"
              wrap={false}
              secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
              contentAfter={<Badge>4</Badge>}
            />
          </List>
        </TabPanel>
        <TabPanel id="work" className="Tab-panel">
          <Text>Work</Text>
        </TabPanel>
        <TabPanel id="favorites" className="Tab-panel">
          <Text>Favorites</Text>
        </TabPanel>
        <TabPanel id="groups" className="Tab-panel">
          <Text>Groups</Text>
        </TabPanel>
      </Pager>
    </Layer>
  );
}

Using sections

Sections provide sticky headers via the title prop.

/** @jsx jsx */

function Example() {
  return (
    <Layer
      css={{
        overflowY: "scroll",
        maxHeight: "300px",
        maxWidth: "100%",
        width: "450px"
      }}
    >
      <List>
        <ListSection title="Friends">
          <ListItem
            contentBefore={
              <Avatar name={"Ben McMahen"} src={faker.image.avatar()} />
            }
            primary="Ben McMahen"
            secondary="Minim do minim cupidatat veniam aliquip sunt exercitation enim nisi nulla."
            contentAfter={<IconChevronRight />}
          />
          <ListItem
            contentBefore={
              <Avatar name={"Ben McMahen"} src={faker.image.avatar()} />
            }
            primary="Joe Chen"
            secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
            contentAfter={<IconChevronRight />}
          />
        </ListSection>
        <ListSection title="Family">
          <ListItem
            contentBefore={
              <Avatar name={"Lynn Apple"} src={faker.image.avatar()} />
            }
            primary="Lynn Apple"
            secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
            contentAfter={<IconChevronRight />}
          />
          <ListItem
            contentBefore={
              <Avatar name={"Mary Joe"} src={faker.image.avatar()} />
            }
            primary="Mary Joe"
            secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
            contentAfter={<IconChevronRight />}
          />
          <ListItem
            contentBefore={
              <Avatar name={"Mary Joe"} src={faker.image.avatar()} />
            }
            primary="Mary Joe"
            secondary="Proident irure cupidatat cupidatat elit eiusmod mollit."
            contentAfter={<IconChevronRight />}
          />
        </ListSection>
      </List>
    </Layer>
  );
}

Disabling wrap

You can disable text wrapping via the wrap prop.

<Layer style={{ overflow: "hidden", maxWidth: "100%", width: "450px" }}>
  <List>
    <ListItem
      wrap={false}
      contentBefore={<Avatar name={"Ben McMahen"} src={faker.image.avatar()} />}
      primary="Ben McMahen"
      secondary="Minim do minim cupidatat veniam aliquip sunt exercitation enim nisi nulla."
      contentAfter={<IconChevronRight />}
    />
  </List>
</Layer>

You can use an anchor by setting the component and href props. Alternatively, you could use something like React-router's Link component.

<List>
  <ListItem
    component="a"
    href="/"
    contentBefore={<Avatar name={"Ben McMahen"} src={faker.image.avatar()} />}
    primary="Ben McMahen"
    wrap={false}
    secondary="Minim do minim cupidatat veniam aliquip sunt exercitation enim nisi nulla."
    contentAfter={<IconChevronRight />}
  />
</List>

Non interactive

By default, list items are treated as interactive buttons. You can disable this.

<List>
  <ListItem
    interactive={false}
    contentBefore={<Avatar name={"Ben McMahen"} src={faker.image.avatar()} />}
    primary="Ben McMahen"
    wrap={false}
    contentAfter={<Button onPress={() => alert("Hey!")}>Learn more</Button>}
  />
</List>

Loading example

You can combine ListItem with Skeleton to create loading states.

<List>
  <ListItem
    interactive={false}
    aria-live="polite"
    aria-busy="true"
    primary={<Skeleton style={{ width: "100px" }} />}
    secondary={<Skeleton />}
    contentBefore={
      <Skeleton
        style={{ width: "3.27rem", height: "3.27rem", borderRadius: "50%" }}
      />
    }
  />
</List>

Infinite scroll

You can combine useInfiniteScroll, ScrollView, and List to create infinitely scrollable lists. For very long lists you may want to consider using a windowing infinite scroller instead.

/** @jsx jsx */

function Example() {
  const ref = React.useRef();
  const [items, setItems] = React.useState(
    Array.from(new Array(10)).map(() => faker.name.firstName())
  );

  function fetchdata() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve();
      }, 500);
    });
  }

  const [page, setPage] = React.useState(0);

  const [fetching] = useInfiniteScroll({
    container: ref,
    hasMore: page < 4,
    onFetch: () => {
      return fetchdata().then(() => {
        setItems([
          ...items,
          ...Array.from(new Array(10)).map(() => faker.name.firstName())
        ]);
        setPage(page + 1);
      });
    }
  });

  return (
    <ScrollView overflowY css={{ height: "300px" }} innerRef={ref}>
      <List>
        {items.map(item => (
          <ListItem key={item} primary={item} />
        ))}
        {fetching && (
          <ListItem
            interactive={false}
            aria-live="polite"
            aria-busy="true"
            primary={<Skeleton animated css={{ width: "150px" }} />}
          />
        )}
      </List>
    </ScrollView>
  );
}

API

List
childrenReactNode
A series of ListItem elements
cssInterpolationWithTheme<any>
ListItem
onPressOnPressFunction
componentReactType<any>
contentBeforeReactNode
An icon or avatar to appear to the left of the text content
contentAfterReactNode
an icon to appear to the right of the text content
interactiveboolean
whether the list item is interactive (ie., can be clicked as a button)
childrenReactNode
optional third row of content
wrapboolean
whether primary and secondary text should be wrapped
primary*ReactNode
The primary text content of the list item
secondaryReactNode
the secondary text content
cssInterpolationWithTheme<any>
ListSection
title*string
A title of the section
stickyboolean
whether the title should stick to the top of the scrollable content
cssInterpolationWithTheme<any>