mittwald Flow Logo

Components

List

Die List bildet einen strukturierten Rahmen für mehrere ListItems und bietet Funktionen wie Sortierung, Filter und Suche.GitHub

Playground

Mit typedList<T> lässt sich eine List für einen bestimmten Datentyp erzeugen.

Im Tab Develop stehen verschiedene Anleitungen zum technischen Aufbau bereit.

import {
  ActionGroup,
  AlertBadge,
  Avatar,
  Button,
  ContextMenu,
  Heading,
  IconDomain,
  IconSubdomain,
  MenuItem,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      defaultViewMode="list"
    >
      <DomainList.StaticData data={domains} />
      <ActionGroup>
        <Button color="accent">Anlegen</Button>
      </ActionGroup>
      <DomainList.Search />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
      />
      <DomainList.Sorting
        property="hostname"
        name="Domain A bis Z"
        direction="asc"
      />
      <DomainList.Sorting
        property="hostname"
        name="Domain Z bis A"
        direction="desc"
      />
      <DomainList.Table>
        <DomainList.TableHeader>
          <DomainList.TableColumn>
            Name
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Type
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            TLD
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Hostname
          </DomainList.TableColumn>
        </DomainList.TableHeader>

        <DomainList.TableBody>
          <DomainList.TableRow>
            <DomainList.TableCell>
              {(domain) => domain.domain}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.type}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.tld}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.hostname}
            </DomainList.TableCell>
          </DomainList.TableRow>
        </DomainList.TableBody>
      </DomainList.Table>
      <DomainList.Item
        textValue={(domain) => domain.domain}
        showTiles
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar
              color={
                domain.type === "Domain" ? "blue" : "teal"
              }
            >
              {domain.type === "Domain" ? (
                <IconDomain />
              ) : (
                <IconSubdomain />
              )}
            </Avatar>
            <Heading>
              {domain.hostname}
              {!domain.verified && (
                <AlertBadge status="warning">
                  Unverifiziert
                </AlertBadge>
              )}
            </Heading>
            <Text>{domain.type}</Text>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
              <MenuItem>Löschen</MenuItem>
            </ContextMenu>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}

Ansichten

Die Liste unterstützt die Ansichten Liste, Raster und Tabelle. Wird mehr als eine Ansicht verwendet, kann über ein Menü zwischen den Ansichten gewechselt werden. Die Default-Ansicht wird über das Property defaultViewMode festgelegt.

Listenansicht

Nutze <List.Item />, um die List in der Listenansicht darzustellen.

import {
  AlertBadge,
  Avatar,
  ContextMenu,
  Heading,
  IconDomain,
  IconSubdomain,
  MenuItem,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      hidePagination
    >
      <DomainList.StaticData data={domains} />

      <DomainList.Item
        textValue={(domain) => domain.domain}
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar
              color={
                domain.type === "Domain" ? "blue" : "teal"
              }
            >
              {domain.type === "Domain" ? (
                <IconDomain />
              ) : (
                <IconSubdomain />
              )}
            </Avatar>
            <Heading>
              {domain.hostname}
              {!domain.verified && (
                <AlertBadge status="warning">
                  Unverifiziert
                </AlertBadge>
              )}
            </Heading>
            <Text>{domain.type}</Text>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
              <MenuItem>Löschen</MenuItem>
            </ContextMenu>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}

Rasteransicht

Für die Rasteransicht wird ebenfalls das <List.Item /> verwendet. Nutze showTiles, um diese Ansicht zu aktivieren. Die Listenansicht kann deaktiviert werden, indem showList auf false gesetzt wird.

Über das maxTileWidth-Property lässt sich die maximale Breite der Kacheln steuern.

import {
  AlertBadge,
  Avatar,
  ContextMenu,
  Heading,
  IconDomain,
  IconSubdomain,
  MenuItem,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      hidePagination
      defaultViewMode="tiles"
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Item
        textValue={(domain) => domain.domain}
        showTiles
        showList={false}
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar
              color={
                domain.type === "Domain" ? "blue" : "teal"
              }
            >
              {domain.type === "Domain" ? (
                <IconDomain />
              ) : (
                <IconSubdomain />
              )}
            </Avatar>
            <Heading>
              {domain.hostname}
              {!domain.verified && (
                <AlertBadge status="warning">
                  Unverifiziert
                </AlertBadge>
              )}
            </Heading>
            <Text>{domain.type}</Text>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
              <MenuItem>Löschen</MenuItem>
            </ContextMenu>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}

Tabellenansicht

Nutze <List.Table />, um die List als Table darzustellen.

import { typedList } from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={4}
      defaultViewMode="table"
      aria-label="Domains"
      hidePagination
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Table>
        <DomainList.TableHeader>
          <DomainList.TableColumn>
            Name
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Type
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            TLD
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Hostname
          </DomainList.TableColumn>
        </DomainList.TableHeader>

        <DomainList.TableBody>
          <DomainList.TableRow>
            <DomainList.TableCell>
              {(domain) => domain.domain}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.type}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.tld}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.hostname}
            </DomainList.TableCell>
          </DomainList.TableRow>
        </DomainList.TableBody>
      </DomainList.Table>
    </DomainList.List>
  );
}

ListItems

In einer List lassen sich ListItems mit unterschiedlichen Interaktions- und Aufbaumöglichkeiten einsetzen.

Mit Link

Ein ListItem bietet das Property href an, um das Element zu verlinken.

import {
  Avatar,
  ContextMenu,
  Heading,
  IconDomain,
  MenuItem,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const List = typedList<Domain>();

  return (
    <List.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
    >
      <List.StaticData data={domains} />
      <List.Item
        href={() => "#"}
        textValue={(domain) => domain.domain}
      >
        {(domain) => (
          <List.ItemView>
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
            </ContextMenu>
          </List.ItemView>
        )}
      </List.Item>
    </List.List>
  );
}

Mit Accordion

Das Accordion-Verhalten wird über die accordion-Property aktiviert. Dadurch lässt sich ein ListItem per Klick ein- oder ausklappen. Der erweiterte Inhalt wird in <Content slot="bottom" /> platziert.

import {
  Avatar,
  Content,
  Heading,
  IconDomain,
  ListItemView,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const List = typedList<Domain>();

  return (
    <List.List
      batchSize={2}
      hidePagination
      accordion
      aria-label="Domains"
    >
      <List.StaticData data={domains} />
      <List.Item textValue={(domain) => domain.domain}>
        {(domain) => (
          <ListItemView>
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>
            <Text>{domain.type}</Text>
            <Content slot="bottom">Mehr Inhalt</Content>
          </ListItemView>
        )}
      </List.Item>
    </List.List>
  );
}

Mit Checkboxen

Checkboxen in einem ListItem werden automatisch am Anfang der Zeile angeordnet. Die Funktionalität der Checkbox wird nicht von der List gesteuert und muss individuell implementiert werden. Achte bei der Implementierung jedoch darauf, dass die gesamte Zeile zur Auswahl des Elements genutzt werden kann. Nutze dafür onAction der List.

import {
  Avatar,
  Checkbox,
  Heading,
  IconDomain,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";
import { useState } from "react";

export default () => {
  const List = typedList<Domain>();

  const [selectedDomains, setSelectedDomains] = useState<
    Domain[]
  >([]);

  const onSelected = (
    domain: Domain,
    selected: boolean,
  ) => {
    if (selected) {
      setSelectedDomains((prev) => [...prev, domain]);
    } else {
      setSelectedDomains((prev) =>
        prev.filter((d) => d.id !== domain.id),
      );
    }
  };

  const isSelected = (domain: Domain) => {
    return (
      selectedDomains.find((d) => d.id === domain.id) !==
      undefined
    );
  };

  return (
    <List.List
      hidePagination
      batchSize={2}
      aria-label="Domains"
      onAction={(domain) => {
        onSelected(domain, !isSelected(domain));
      }}
    >
      <List.StaticData data={domains} />
      <List.Item
        showTiles
        textValue={(domain) => domain.hostname}
      >
        {(domain) => (
          <List.ItemView>
            <Checkbox
              isSelected={isSelected(domain)}
              onChange={(value) =>
                onSelected(domain, value)
              }
              aria-label={`${domain.hostname} auswählen`}
            />
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>
            <Text>{domain.type}</Text>
          </List.ItemView>
        )}
      </List.Item>

      <List.Table>
        <List.TableHeader>
          <List.TableColumn>
            <Checkbox
              aria-label="Alle auswählen"
              onChange={(v) =>
                setSelectedDomains(v ? domains : [])
              }
            />
          </List.TableColumn>
          <List.TableColumn>Domain</List.TableColumn>
        </List.TableHeader>
        <List.TableBody>
          <List.TableRow>
            <List.TableCell>
              {(domain) => (
                <Checkbox
                  isSelected={isSelected(domain)}
                  onChange={(value) =>
                    onSelected(domain, value)
                  }
                  aria-label={`${domain.hostname} auswählen`}
                />
              )}
            </List.TableCell>
            <List.TableCell>
              {(domain) => domain.hostname}
            </List.TableCell>
          </List.TableRow>
        </List.TableBody>
      </List.Table>
    </List.List>
  );
}

Mit Content Slots

In einem ListItem kann zusätzlicher <Content/ > (Top und Bottom Content) platziert werden. Die Position wird über das slot-Property gesteuert.

import {
  Avatar,
  Content,
  ContextMenu,
  Heading,
  IconDomain,
  MenuItem,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const List = typedList<Domain>();

  return (
    <List.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
    >
      <List.StaticData data={domains} />
      <List.Item
        showTiles
        textValue={(domain) => domain.domain}
      >
        {(domain) => (
          <List.ItemView>
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>

            <Content slot="top">Top Content</Content>
            <Content slot="bottom">Bottom Content</Content>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
            </ContextMenu>
          </List.ItemView>
        )}
      </List.Item>
    </List.List>
  );
}

Mit ColumnLayout

Dem ListItem können die ColumnLayout Properties s, m und l mitgegeben werden, um das Seitenverhältnis sowie das Umbruchverhalten von Header und Content zu steuern.

import {
  Avatar,
  Content,
  ContextMenu,
  Heading,
  IconEmail,
  Label,
  MenuItem,
  ProgressBar,
  typedList,
} from "@mittwald/flow-react-components";

export default () => {
  const List = typedList<{ mail: string }>();

  return (
    <List.List
      batchSize={2}
      aria-label="E-Mail-Adressen"
      hidePagination
    >
      <List.StaticData
        data={[
          { mail: "john@doe.com" },
          { mail: "max@mustermann.de" },
        ]}
      />
      <List.Item textValue={(mail) => mail.mail}>
        {(mail) => (
          <List.ItemView l={[3, 1]} m={[2, 1]} s={[1]}>
            <Avatar>
              <IconEmail />
            </Avatar>
            <Heading>{mail.mail}</Heading>

            <Content>
              <ProgressBar size="s" value={50}>
                <Label>Speicherplatz</Label>
              </ProgressBar>
            </Content>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
            </ContextMenu>
          </List.ItemView>
        )}
      </List.Item>
    </List.List>
  );
}

Da für die ColumnLayout-Spalten auch null gesetzt werden kann, ist es möglich, nicht zwingend benötigten Content in kleineren Ansichten auszublenden. In diesem Fall werden auch die entsprechenden Content Slots nicht angezeigt.

import {
  Avatar,
  Content,
  ContextMenu,
  Heading,
  IconEmail,
  Label,
  MenuItem,
  ProgressBar,
  typedList,
} from "@mittwald/flow-react-components";

export default () => {
  const List = typedList<{ mail: string }>();

  return (
    <div style={{ width: 400 }}>
      <List.List
        batchSize={2}
        aria-label="E-Mail-Adressen"
        hidePagination
      >
        <List.StaticData
          data={[
            { mail: "john@doe.com" },
            { mail: "max@mustermann.de" },
          ]}
        />
        <List.Item textValue={(mail) => mail.mail}>
          {(mail) => (
            <List.ItemView
              l={[3, 1]}
              m={[2, 1]}
              s={[1, null]}
            >
              <Avatar>
                <IconEmail />
              </Avatar>
              <Heading>{mail.mail}</Heading>

              <Content>
                <ProgressBar size="s" value={50}>
                  <Label>Speicherplatz</Label>
                </ProgressBar>
              </Content>

              <ContextMenu>
                <MenuItem>Details anzeigen</MenuItem>
              </ContextMenu>
            </List.ItemView>
          )}
        </List.Item>
      </List.List>
    </div>
  );
}

Mit ActionGroup

Verwende eine ActionGroup innerhalb des <Content />, um Buttons in der List zu platzieren.

import {
  ActionGroup,
  Avatar,
  Button,
  Content,
  Heading,
  IconDomain,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const List = typedList<Domain>();

  return (
    <List.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
    >
      <List.StaticData data={domains} />
      <List.Item textValue={(domain) => domain.domain}>
        {(domain) => (
          <List.ItemView>
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>

            <Content>
              <ActionGroup>
                <Button variant="soft" color="secondary">
                  Bearbeiten
                </Button>
                <Button variant="soft" color="danger">
                  Löschen
                </Button>
              </ActionGroup>
            </Content>
          </List.ItemView>
        )}
      </List.Item>
    </List.List>
  );
}

Einstellmöglichkeiten

Die List bietet Sortierung, Filter, Suche und Pagination an. Detaillierte Anleitungen zu den einzelnen Einstellmöglichkeiten stehen unter dem Develop-Tab der List zur Verfügung.

import {
  ActionGroup,
  AlertBadge,
  Avatar,
  Button,
  ContextMenu,
  Heading,
  IconDomain,
  IconSubdomain,
  MenuItem,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      defaultViewMode="list"
    >
      <DomainList.StaticData data={domains} />
      <ActionGroup>
        <Button color="accent">Anlegen</Button>
      </ActionGroup>
      <DomainList.Search />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
      />
      <DomainList.Sorting
        property="hostname"
        name="Domain A bis Z"
        direction="asc"
      />
      <DomainList.Sorting
        property="hostname"
        name="Domain Z bis A"
        direction="desc"
      />
      <DomainList.Table>
        <DomainList.TableHeader>
          <DomainList.TableColumn>
            Name
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Type
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            TLD
          </DomainList.TableColumn>
          <DomainList.TableColumn>
            Hostname
          </DomainList.TableColumn>
        </DomainList.TableHeader>

        <DomainList.TableBody>
          <DomainList.TableRow>
            <DomainList.TableCell>
              {(domain) => domain.domain}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.type}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.tld}
            </DomainList.TableCell>
            <DomainList.TableCell>
              {(domain) => domain.hostname}
            </DomainList.TableCell>
          </DomainList.TableRow>
        </DomainList.TableBody>
      </DomainList.Table>
      <DomainList.Item
        textValue={(domain) => domain.domain}
        showTiles
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar
              color={
                domain.type === "Domain" ? "blue" : "teal"
              }
            >
              {domain.type === "Domain" ? (
                <IconDomain />
              ) : (
                <IconSubdomain />
              )}
            </Avatar>
            <Heading>
              {domain.hostname}
              {!domain.verified && (
                <AlertBadge status="warning">
                  Unverifiziert
                </AlertBadge>
              )}
            </Heading>
            <Text>{domain.type}</Text>

            <ContextMenu>
              <MenuItem>Details anzeigen</MenuItem>
              <MenuItem>Löschen</MenuItem>
            </ContextMenu>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}

Sortierung

Nutze <List.Sorting /> innerhalb der List, um eine Sortiermethode anzulegen.

Filter

Über <List.Filter /> lassen sich Filtermöglichkeiten für die List anlegen.

Suche

Verwende <List.Search /> innerhalb der List, um ein SearchField anzuzeigen. Standardmäßig wird die Suche automatisch gestartet. Soll die Suche nur beim Drücken auf Enter ausgelöst werden, kann das Property autoSubmit auf false gesetzt werden.

Pagination

Die Pagination ist standardmäßig bei jeder List aktiviert, kann jedoch über hidePagination deaktiviert werden. Über die batchSize-Property kann festgelegt werden, wie viele Einträge gleichzeitig angezeigt werden sollen.


Kombiniere mit ...

ActionGroup

Verwende <ActionGroup /> innerhalb der List, um eine ActionGroup anzuzeigen. Hier können eine oder mehrere Aktionen definiert werden, die sich direkt auf die Liste beziehen.

import {
  ActionGroup,
  Avatar,
  Button,
  Heading,
  IconDomain,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/03-components/structure/list/examples/domainApi";

export default () => {
  const DomainList = typedList<Domain>();

  return (
    <DomainList.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
    >
      <DomainList.StaticData data={domains} />
      <ActionGroup>
        <Button color="accent">Anlegen</Button>
      </ActionGroup>
      <DomainList.Item
        textValue={(domain) => domain.domain}
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar>
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>
            <Text>{domain.type}</Text>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}

Summary

Verwende eine <ListSummary />, um eine Zusammenfassung anzuzeigen, beispielsweise die Gesamtsumme der Beträge. Über das position-Property wird festgelegt, ob die Summary oberhalb oder unterhalb der List erscheint.

import {
  Flex,
  Heading,
  ListItemView,
  ListSummary,
  Text,
  typedList,
} from "@mittwald/flow-react-components";

export default () => {
  const InvoiceList = typedList<{
    id: string;
    amount: string;
  }>();

  return (
    <InvoiceList.List
      batchSize={2}
      hidePagination
      aria-label="Rechnungen"
    >
      <ListSummary position="bottom">
        <Flex justify="end">
          <Text>
            <b>Gesamt: 37,00 €</b>
          </Text>
        </Flex>
      </ListSummary>
      <InvoiceList.StaticData
        data={[
          {
            id: "Rechnung 1",
            amount: "25,00 €",
          },
          {
            id: "Rechnung 2",
            amount: "12,00 €",
          },
        ]}
      />

      <InvoiceList.Item textValue={(invoice) => invoice.id}>
        {(invoice) => (
          <ListItemView>
            <Heading>{invoice.id}</Heading>
            <Text>{invoice.amount}</Text>
          </ListItemView>
        )}
      </InvoiceList.Item>
    </InvoiceList.List>
  );
}

Overview