# List

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

## Overview

# 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.

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

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

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      defaultViewMode="list"
      getItemId={(domain) => domain.id}
    >
      <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="Alphabetisch"
        direction="asc"
        directionName="aufsteigend"
        defaultEnabled
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="desc"
        directionName="absteigend"
      />
      <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.

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

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

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      hidePagination
      getItemId={(domain) => domain.id}
    >
      <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.

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

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

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      hidePagination
      defaultViewMode="tiles"
      getItemId={(domain) => domain.id}
    >
      <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](/04-components/structure/table/overview) darzustellen.

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

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

  return (
    <DomainList.List
      batchSize={4}
      defaultViewMode="table"
      aria-label="Domains"
      hidePagination
      getItemId={(domain) => domain.id}
    >
      <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.

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

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

  return (
    <List.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <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.

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

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

  return (
    <List.List
      batchSize={2}
      hidePagination
      accordion
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <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](/04-components/form-controls/checkbox/overview) 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.

```tsx
import {
  Avatar,
  Checkbox,
  Heading,
  IconDomain,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/04-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));
      }}
      getItemId={(domain) => domain.id}
    >
      <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.

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

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

  return (
    <List.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <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](/04-components/structure/column-layout/overview) Properties `s`,
`m` und `l` mitgegeben werden, um das Seitenverhältnis sowie das
Umbruchverhalten von Header und Content zu steuern.

```tsx
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.

```tsx
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](/04-components/actions/button/overview) in der List zu platzieren.

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

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

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

            <Content>
              <ActionGroup>
                <Button
                  aria-label="Bearbeiten"
                  variant="plain"
                  color="secondary"
                >
                  <IconEdit />
                </Button>
                <Button
                  aria-label="Löschen"
                  variant="plain"
                  color="secondary"
                >
                  <IconDelete />
                </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.

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

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

  return (
    <DomainList.List
      batchSize={4}
      aria-label="Domains"
      defaultViewMode="list"
      getItemId={(domain) => domain.id}
    >
      <DomainList.StaticData data={domains} />
      <ActionGroup>
        <Button color="accent">Anlegen</Button>
      </ActionGroup>
      <DomainList.Search />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
      />
      <DomainList.Filter
        property="tld"
        mode="some"
        name="TLD"
        priority="secondary"
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="asc"
        defaultEnabled
        directionName="aufsteigend"
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="desc"
        directionName="absteigend"
      />
      <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.
Über `priority` kann eingestellt werden, ob Filter immer sichtbar sein sollen
(primary) oder nur in einem "Alle Filter" Modal angezeigt werden (secondary).
"Alle Filter" wird automatisch angezeigt, sobald es secondary Filter gibt.

### Date Range Filter

Mit dem `mode="dateRange"` kannst du einen Filter definieren, der es ermöglicht,
einen Zeitraum auszuwählen. So lassen sich Einträge gezielt zwischen einem
Start- und Enddatum eingrenzen.

```tsx
import { typedList } from "@mittwald/flow-react-components";
import {
  type CalendarDate,
  getLocalTimeZone,
  today,
} from "@internationalized/date";

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

  return (
    <InvoiceList.List
      aria-label="Rechnungen"
      defaultViewMode="table"
      getItemId={(domain) => domain.id}
    >
      <InvoiceList.StaticData
        data={[
          {
            id: "RG100000",
            date: today(getLocalTimeZone()),
          },
          {
            id: "RG100001",
            date: today(getLocalTimeZone()).subtract({
              days: 7,
            }),
          },
          {
            id: "RG100002",
            date: today(getLocalTimeZone()).subtract({
              days: 14,
            }),
          },
        ]}
      />
      <InvoiceList.Filter
        property="date"
        mode="dateRange"
        name="Datum"
        dateRangeOptions={{
          maxValue: today(getLocalTimeZone()),
        }}
      />
      <InvoiceList.Table>
        <InvoiceList.TableHeader>
          <InvoiceList.TableColumn>
            Rechnung
          </InvoiceList.TableColumn>
          <InvoiceList.TableColumn>
            Datum
          </InvoiceList.TableColumn>
        </InvoiceList.TableHeader>

        <InvoiceList.TableBody>
          <InvoiceList.TableRow>
            <InvoiceList.TableCell>
              {(invoice) => invoice.id}
            </InvoiceList.TableCell>
            <InvoiceList.TableCell>
              {(invoice) =>
                `${invoice.date.day}.${invoice.date.month}.${invoice.date.year}`
              }
            </InvoiceList.TableCell>
          </InvoiceList.TableRow>
        </InvoiceList.TableBody>
      </InvoiceList.Table>
    </InvoiceList.List>
  );
}
```

## Suche

Verwende `<List.Search />` innerhalb der List, um ein
[SearchField](/04-components/form-controls/search-field/overview) 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.

## Empty View

Mit `emptyView` kann eine benutzerdefinierte Ansicht angezeigt werden, wenn die
Liste keine Einträge enthält. So lässt sich beispielsweise eine Illustration mit
einem Hinweistext anzeigen, um die Nutzer zu informieren, dass keine Daten
vorhanden sind, und ihnen gegebenenfalls Tipps zu geben, wie sie Daten
hinzufügen können.

In der Regel sollte als Empty View eine IllustratedMessage verwendet werden, um
eine konsistente Nutzererfahrung zu gewährleisten.

```tsx
import {
  Heading,
  IconDomain,
  IllustratedMessage,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import { type Domain } from "@/content/04-components/structure/list/examples/domainApi";

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

  const emptyView = (
    <IllustratedMessage>
      <IconDomain />
      <Heading>Keine Domains gefunden</Heading>
      <Text>Füge neue Domains hinzu, um zu starten.</Text>
    </IllustratedMessage>
  );

  return (
    <List.List aria-label="Domains" emptyView={emptyView}>
      <List.StaticData data={[]} />
      <List.Item>{() => null}</List.Item>
    </List.List>
  );
}
```

---

# Kombiniere mit ...

## ActionGroup

Verwende `<ActionGroup />` innerhalb der List, um eine
[ActionGroup](/04-components/actions/action-group/overview) anzuzeigen. Hier
können eine oder mehrere Aktionen definiert werden, die sich direkt auf die
Liste beziehen.

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

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

  return (
    <DomainList.List
      batchSize={2}
      hidePagination
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <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.

```tsx
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"
      getItemId={(invoice) => invoice.id}
    >
      <ListSummary position="bottom">
        <Flex justify="end">
          <Text>
            <strong>Gesamt: 37,00 €</strong>
          </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>
  );
}
```


## Develop

Die List Component erlaubt das Rendern und Verwalten von Daten in einer
strukturierten Liste. Daten können entweder **statisch** oder **asynchron**, z.
B. über eine API, geladen werden.

# Möglichkeiten zum Laden von Daten

## 1. Statische Daten

Für die Anzeige statischer Daten kann die Component `<StaticData />` verwendet
werden:

```tsx
<List.StaticData data={dataArray} />
```

- `dataArray`: Ein Array mit den Daten, die direkt in der List gerendert werden.
- Diese Variante ist einfach und benötigt keine zusätzliche Logik für das
  Nachladen oder Filtern.

## 2. Dynamische Daten (Asynchrones Laden)

Mit `<LoaderAsync>` können Daten dynamisch aus einer API oder anderen
asynchronen Quellen nachgeladen werden:

```tsx
<List.LoaderAsync>
  {async (options) => {
    const response = await fetchDataFromAPI(options);
    return {
      data: response.items,
      itemTotalCount: response.totalCount,
    };
  }}
</List.LoaderAsync>
```

## 3. Laden über Hooks (z.B. TanStack Query oder SWR)

Mit `<LoaderHooks>` können Daten dynamisch über React Hooks nachgeladen werden.
Der Einsatz von Suspense ist hierbei erforderlich.

```tsx
import { useSuspenseQuery } from "@tanstack/react-query";

<List.LoaderHooks>
  {async (options) => {
    const response = useSuspenseQuery({
      queryKey: ["api", options],
      queryFn: () => fetchDataFromAPI(options),
    });
    return {
      data: response.items,
      itemTotalCount: response.totalCount,
    };
  }}
</List.LoaderHooks>;
```

### Verhalten & Features

- **Ladeanimation:** Während die Daten geladen werden, wird eine Ladeanimation
  angezeigt.
- **Server-seitige Funktionen:**
  - **Pagination** (`manualPagination`): Aktiviert das serverseitige Paging.
  - **Sortierung** (`manualSorting`): Die Sortierung erfolgt auf dem Server.
  - **Filterung** (`manualFiltering`): Filter werden nicht client-seitig
    angewendet, sondern an den Server weitergeleitet.
  - **Suche:** Kann ebenfalls serverseitig erfolgen.

## Optionen für die Async Loader Function

Die `<LoaderAsync>`-Component benötigt eine **Async Loader Function**, die die
Daten anhand von Steuerungsoptionen lädt. Diese Funktion erhält ein
`options`-Objekt mit den folgenden Parametern:

### Struktur des `options`-Objekts

| Property       | Typ                                                                    | Beschreibung                                                                                             |
| -------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `filtering`    | `{ [key: string]: { mode: "all" \| "some" \| "one"; values: any[] } }` | Enthält Filter für die Daten. Jedes Key-Value-Paar repräsentiert eine Filterbedingung für ein Datenfeld. |
| `searchString` | `string`                                                               | Der eingegebene Suchbegriff.                                                                             |
| `pagination`   | `{ offset: number; limit: number }`                                    | Enthält Offset (Startpunkt) und Limit (maximale Anzahl an Datensätzen).                                  |
| `sorting`      | `{ [key: string]: "asc" \| "desc" }`                                   | Gibt an, nach welchen Datenfeldern sortiert werden soll.                                                 |

### Rückgabewert der Async Loader Function

Die Funktion muss ein **Object** mit folgender Struktur zurückgeben:

```tsx
return {
  data: [
    /* Array mit den Datensätzen */
  ],
  itemTotalCount: 100, // Gesamtanzahl der Datensätze (für Pagination)
};
```

| Property         | Typ      | Beschreibung                                                   |
| ---------------- | -------- | -------------------------------------------------------------- |
| `data`           | `any[]`  | Array der geladenen Daten.                                     |
| `itemTotalCount` | `number` | Gesamtanzahl der Datensätze (nur bei Pagination erforderlich). |

---

# Filter

In der Regel werden Filter für ein Property der List gesetzt:

```tsx
<List.Filter mode="some" name="Status" property="status" />
```

Die Anzeige des Filter-Values kann angepasst werden, um z. B. Übersetzungen zu
ermöglichen:

```tsx
<List.Filter mode="some" name="Status" property="status">
  {(value) => translate(value)}
</List.Filter>
```

Es gibt die Möglichkeit, eigene Properties zu verwenden, die nicht in der List
vorkommen . Hierfür muss dem `property` ein "$" vorangestellt werden:

```tsx
<List.Filter
  matcher={(filterValue, item) => filterValue === "disabled" ? !!item.disabled : item.status === filterValue}
  mode="some"
  name="Filter"
  property="$filter"
  values={["active", "inactive", "disabled"]}
>
```

## Filter Properties

| Property          | Typ                               | Beschreibung                                                                |
| ----------------- | --------------------------------- | --------------------------------------------------------------------------- |
| `defaultSelected` | `string[]`                        | Array der als default gesetzten Filter                                      |
| `matcher`         | `FilterMatcher<T, TProp, string>` | Definiert eine eigene Filterlogik für die Listenelemente                    |
| `mode`            | `"all" \| "some" \| "one"`        | Bestimmt, wie mehrere ausgewählte Filterwerte miteinander kombiniert werden |
| `name`            | `string`                          | Der Anzeigename des Filters                                                 |
| `property`        | `string`                          | Das für die Filterung verwendete Property                                   |
| `values`          | `string[]`                        | Die Optionen für den Filter                                                 |

---

# Sorting

Die List unterstützt eine Sortierung nach Properties:

```tsx
<List.Sorting defaultEnabled direction="asc" name="Name" property="name" />
```

Es gibt außerdem die Möglichkeit, eine eigene Sortierung zu benutzen:

```tsx
<List.Sorting
  customSortingFn={(a, b) => sortHostnames(a.hostname, b.hostname)}
  direction="asc"
  name="Domain"
  property="$domain"
  directionName="aufsteigend"
/>
```

## Sorting Properties

| Property          | Typ                   | Beschreibung                                                                                                                                  |
| ----------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `customSortingFn` | `SortingFn<T>`        | Möglichkeit eine eigene Sortierfunktion zu definieren                                                                                         |
| `defaultEnabled`  | `boolean \| "hidden"` | Bestimmt, ob die Sortierung als default gesetzt wird, bei "hidden" ist die Sortier-Option nicht sichtbar, wird aber im Hintergrund angewendet |
| `direction`       | `"asc" \| "desc"`     | Auf- oder absteigende Sortierung                                                                                                              |
| `name`            | `string`              | Der Anzeigename der Sortier-Option                                                                                                            |
| `directionName`   | `string`              | Der Anzeigename der Sortierrichtung                                                                                                           |
| `property`        | `string`              | Das für die Sortierung verwendete Property                                                                                                    |

---

# Properties

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `batchSize` | `number` | - | The number of items to be displayed on one page. |
| `hidePagination` | `boolean` | - | - |
| `emptySearchResultView` | `ReactNode` | - | - |
| `emptyView` | `ReactNode` | - | - |
| `children` | `ReactNode` | - | - |
| `wrapWith` | `ReactElement<unknown, string \| JSXElementConstructor<any>>` | - | - |
| `ref` | `Ref<HTMLSpanElement>` | - | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see [React Docs](https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom) |
| `key` | `Key` | - | - |
| `disallowEmptySelection` | `boolean` | - | Whether the collection allows empty selection. |
| `disabledKeys` | `Iterable<Key>` | - | The currently disabled keys in the collection (controlled). |
| `selectionMode` | `SelectionMode` | - | The type of selection that is allowed in the collection. |
| `selectedKeys` | `"all" \| Iterable<Key>` | - | The currently selected keys in the collection (controlled). |
| `defaultSelectedKeys` | `"all" \| Iterable<Key>` | - | The initial selected keys in the collection (uncontrolled). |
| `selectionBehavior` | `SelectionBehavior` | - | - |
| `accordion` | `boolean` | - | - |
| `settingStorageKey` | `string` | - | - |
| `loadingItemsCount` | `number` | - | - |
| `getItemId` | `GetItemId<never>` | - | - |
| `defaultViewMode` | `ListViewMode` | - | - |
| `settingsStorageDefaults` | `ListSettingsStorageDefaults` | - | - |

### Events

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `onChange` | `OnListChanged<never, unknown>` | - | - |
| `onSelectionChange` | `((keys: Selection) => void)` | - | Handler that is called when the selection changes. |
| `onAction` | `ItemActionFn<never>` | - | - |

### Accessibility

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `aria-label` | `string` | - | - |
| `aria-labelledby` | `string` | - | - |


## Guidelines

# Grundlagen

Die List bildet den strukturierten Rahmen für sogenannte **ListItems**.
Alternativ können in der List Elemente in einer
[Table](/04-components/structure/table/overview) dargestellt werden, wenn eine
tabellarische Ansicht mit Spalten sinnvoll ist.

Ein ListItem ist ein Eintrag in der List und repräsentiert ein spezifisches
Element einer übergeordneten Kategorie (z. B. Domains, E-Mail-Adressen, Projekte
…). Dabei werden nur die Informationen angezeigt, die notwendig sind, um das
jeweilige Element zu verstehen.

Die List fasst alle ListItems bzw. Tabelleneinträge zusammen und stellt
Funktionen wie **Sortierung**, **Filterung** und **Suche** bereit. Zusätzlich
kann der User über einen „Mehr
anzeigen“-[Button](/04-components/actions/button/overview) steuern, wie viele
ListItems gleichzeitig angezeigt werden.

## Best practices

Achte bei der Verwendung einer List darauf, dass...

- eine passende Ansicht angeboten wird. Eine Rasteransicht ist z. B. sinnvoll,
  wenn das Bild eines ListItems für den Userflow entscheidend ist. Gibt es
  mehrere Anwendungsfälle, sollte der User zwischen verschiedenen Ansichten
  wechseln können.
- eine Standardsortierung gewählt wird, die dem häufigsten Anwendungsfall
  entspricht (z. B. „Neueste zuerst“ bei einer Änderungshistorie).
- nur Sortieroptionen angeboten werden, die für den User einen echten Mehrwert
  bieten.
- bei umfangreichen Lists Filter eingesetzt und die Filterkategorien sinnvoll
  gruppiert werden (z. B. nach Typ, Größe oder Status).

## Verwendung

Verwende eine List, um ...

- viele Elemente (z. B. Domains, Projekte, Rechnungspositionen ...) in Form von
  ListItems kompakt darzustellen.
- einen einfachen Weg zu schaffen, ein spezifisches ListItem über Filter,
  Sortierung oder Suche zu finden.
- über ListItems zur Detailseite eines Eintrags zu navigieren.

---

# Anwendung

## Position

- Die List wird häufig auf Übersichtsseiten eingesetzt, z. B. um alle Domains
  auf einen Blick darzustellen. Von dort aus können User in die zugehörigen
  [Detailseiten](/03-patterns/01-patterns/detail-page) navigieren.
- Sie nimmt immer die volle Breite des Inhalts einer
  [LayoutCard](/04-components/structure/layout-card/overview) ein.
- Wenn neben der List weiterer Inhalt auf der Seite vorhanden ist, sollte dieser
  oberhalb platziert werden. Auf diese Weise werden Layout-Verschiebungen durch
  das Nachladen über den „Mehr
  anzeigen“-[Button](/04-components/actions/button/overview) vermieden.

## Ansichten

Die List unterstützt drei verschiedene Ansichten: **Liste, Raster** und
**Tabelle**.

In der Listen- und Rasteransicht werden unterschiedliche Varianten des ListItems
dargestellt. In der Tabellenansicht verwendet die List die
[Table](/04-components/structure/table/overview)-Component. Sind mehrere
Ansichten verfügbar, können User über den
Ansichts-[Button](/04-components/actions/button/overview) zwischen ihnen
wechseln.

- **Liste:** Besonders geeignet, wenn viele Elemente übersichtlich, platzsparend
  und gleichzeitig ansprechend dargestellt werden sollen.
- **Raster:** Sinnvoll, wenn die Anzahl der Elemente überschaubar ist oder die
  visuelle Darstellung des Elements im Vordergrund steht. Das ListItem sollte
  hier nur wenige Informationen enthalten.
- **Tabelle:** Ideal für Daten, die schnell erfassbar sein müssen, während die
  optische Gestaltung eine untergeordnete Rolle spielt.

## Sortierung

Der Sortierungs-[Button](/04-components/actions/button/overview) wird stets
links neben dem Filter-Button angezeigt. Die Standardsortierung sollte dem
häufigsten Anwendungsfall entsprechen (z. B. „Neueste zuerst“ bei einer
Änderungshistorie). Die aktive Sortierung wird im Sortierungs-Button angezeigt
(siehe Abschnitt Writing guidelines).

## Filter

Ein Klick auf den Filter-Button öffnet ein
[ContextMenu](/04-components/actions/context-menu/overview), in dem Filter
aktiviert oder deaktiviert werden können.

- Unterstützt der Filter eine **Mehrfachauswahl**, werden die Optionen als
  [Checkbox](/04-components/form-controls/checkbox/overview) dargestellt.
- Erfordert ein Filter **Einzelauswahl**, werden die Optionen als
  [RadioGroup](/04-components/form-controls/radio-group/overview) angezeigt.
- Jeder **aktive Filter** wird durch eine
  [Badge](/04-components/status/badge/overview) visualisiert, der per Klick
  einzeln entfernt werden kann. Sind mindestens zwei Filter aktiv, erscheint
  zusätzlich ein “Filter zurücksetzen”-Button.
- Mehrere Filter **derselben Kategorie** (z. B. Status, Art, Größe, Abteilung …)
  sollten in einem eigenen Filter-Button zusammengefasst werden. Filter ohne
  Kategorie können unter einem allgemeinen „Filter“-Button gruppiert werden.

**✅ Do**

Wenn es mehrere Filter einer Kategorie gibt, fasse sie in einem eigenen Button
  mit passender Bezeichnung zusammen.

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

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

  return (
    <DomainList.List
      batchSize={2}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
      hidePagination
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Filter
        property="verified"
        mode="some"
        name="Status"
        values={["Verifiziert", "Unverifiziert"]}
        matcher={(filterValue, verified) => {
          return filterValue === "Verifiziert"
            ? verified
            : !verified;
        }}
      />
      <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>
  );
}
```

**✅ Do**

Hat ein Filter keine spezifische Kategorie, sollte er in einem allgemeinen
  "Filter"-Button platziert werden.

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

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

  return (
    <DomainList.List
      batchSize={2}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
      hidePagination
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Filter
        property="verified"
        mode="some"
        name="Status"
        values={["Verifiziert", "Unverifiziert"]}
        matcher={(filterValue, verified) => {
          return filterValue === "Verifiziert"
            ? verified
            : !verified;
        }}
      />
      <DomainList.Filter
        property="id"
        mode="some"
        name="Filter"
        values={[
          "SSL Zertifikat abgelaufen",
          "Kein Ziel hinterlegt",
        ]}
        matcher={() => true}
      />
      <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>
  );
}
```

## Pagination

Standardmäßig werden maximal 10 ListItems angezeigt. Mit der Option `batchSize`
kann die Anzahl der anfänglichen ListItems angepasst werden. Über den "Mehr
anzeigen"-[Button](/04-components/actions/button/overview) können bei Bedarf
weitere ListItems nachgeladen werden.

Beachte bei der Anpassung der Pagination, dass ...

- nur in den seltensten Fällen mehr als 10 Einträge gleichzeitig sichtbar sein
  müssen. Der User kann über **Suche, Filter** und **Sortierung** gezielt nach
  Einträgen suchen.
- eine List, die beim initialen Seitenaufruf viele Einträge darstellt, die
  Performance beeinträchtigen kann. Gleiches gilt, wenn durch einen Klick auf
  den “Mehr Anzeigen”-Button viele Einträge auf einmal nachgeladen werden.

---

# Inhalt

## ListItems

ListItems sind die zentralen Bestandteile der List. Sie repräsentieren jeweils
ein spezifisches Element und können entweder in einer Listen- oder Rasteransicht
dargestellt werden.

In der **Listenansicht** besteht ein ListItem typischerweise aus:

- **Avatar:** Der [Avatar](/04-components/content/avatar/overview) steht am
  Anfang eines ListItems. Wird ein [Icon](/04-components/content/icon/overview)
  verwendet, sollte es die Kategorie des dargestellten Elements widerspiegeln
  (z. B. ein Domain-Icon bei der Kategorie Domain). Falls der User für das
  Element ein [Image](/04-components/content/image/overview) hochladen kann,
  wird dieses angezeigt. Ist kein Image vorhanden, sollten stattdessen Initialen
  erscheinen.
- **Titel:** Der Titel gibt den vergebenen Namen wieder.
- **Untertitel:** Unterhalb der Benennung können weitere Informationen ergänzt
  werden. Der Aufbau folgt einem festen Muster: Beschreibender Untertitel – 1.
  Information – 2. Information
- **Top Content und Bottom Content:** Neben den Basisinformationen verfügt ein
  ListItem über zusätzliche Bereiche, die flexibel befüllt werden können (siehe
  Overview: Content Slots).
- **Aktionen:** Für Interaktionen mit dem ListItem stehen zwei Varianten zur
  Verfügung. Entweder werden Aktionen über ein
  [ContextMenu](/04-components/actions/context-menu/overview) angeboten, oder
  sie erscheinen direkt als [Buttons](/04-components/actions/button/overview)
  innerhalb des ListItems.
- **Accordion-Funktion:** Optional kann ein ListItem eine Accordion-Funktion
  besitzen.

In der **Rasteransicht** ist die Darstellung kompakter und stärker visuell
ausgerichtet. Der [Avatar](/04-components/content/avatar/overview) wird hier
größer und in eckiger Form angezeigt. Die grundlegenden Funktionen sind
vergleichbar mit denen der Listenansicht, allerdings entfällt in diesem Modus
der Top Content sowie die Accordion-Funktion.

## Tabellenansicht

Wird eine List mit einer Tabellenansicht verwendet, sind die Guidelines der
[Table](/04-components/structure/table/overview) zu beachten.

## IllustratedMessage

Die [IllustratedMessage](/04-components/content/illustrated-message/overview)
wird in zwei Anwendungsfällen verwendet:

- **Keine Einträge vorhanden:** Die List wird durch eine IllustratedMessage
  ersetzt. Diese lädt den User über einen
  [Button](/04-components/actions/button/overview) dazu ein, das erste Element
  zu erstellen.
- **Keine Such- oder Filterergebnisse:** Anstelle der ListItems zeigt die List
  eine IllustratedMessage an, die verdeutlicht, dass keine Ergebnisse vorliegen.

---

# Writing guidelines

## Sortierung

Ist die Standardsortierung aktiv, zeigt der
Sortierungs-[Button](/04-components/actions/button/overview) lediglich
“Sortierung” an. Wählt der User eine spezifische Sortierung aus, wird der
Button-Text entsprechend angepasst. Die Sortiermethode sollte so benannt werden,
dass sofort ersichtlich ist, nach welchem Kriterium und in welcher Reihenfolge
sortiert wird.

**✅ Do**

Benenne die Sortierung so, dass eindeutig ersichtlich ist, wonach und in
  welcher Reihenfolge sortiert wird.

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

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

  return (
    <DomainList.List
      batchSize={2}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
      hidePagination
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="asc"
        directionName="aufsteigend"
        defaultEnabled
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="desc"
        directionName="absteigend"
      />
      <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>
  );
}
```

**⛔️ Don't**

Verzichte auf Sortierformulierungen, die nicht eindeutig verständlich sind
  oder keine klare Reihenfolge vermitteln.

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

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

  return (
    <DomainList.List
      batchSize={2}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
      hidePagination
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Sorting
        property="domain"
        name="Name"
        defaultEnabled
      />
      <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>
  );
}
```

## Filterung

Aktive Filter werden als [Badges](/04-components/status/badge/overview)
dargestellt. Diese sollten selbsterklärend sein. Bei mehrdeutigen Begriffen
empfiehlt es sich, zusätzlichen Kontext anzugeben.

**✅ Do**

Intuitiv verständliche Filter benötigen keinen zusätzlichen Kontext.
  Erklärungsbedürftige Filter sollten hingegen mit weiterem Text versehen
  werden, um ihre Nutzung zu erleichtern.

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

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

  return (
    <DomainList.List
      batchSize={5}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
        values={["Domain", "Subdomain"]}
        defaultSelected={["Domain"]}
      />
      <DomainList.Filter
        property="verified"
        mode="some"
        name="Verifizierung"
        matcher={(filterValue, propertyValue) =>
          filterValue === "Verifiziert"
            ? propertyValue
            : !propertyValue
        }
        defaultSelected={["Unverifiziert"]}
        values={["Verifiziert", "Unverifiziert"]}
      />
      <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>
  );
}
```

**⛔️ Don't**

Bei intuitiven Filtern sollte auf zusätzlichen Text verzichtet werden. Meist
  genügt da ein einzelnes beschreibendes Wort.

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

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

  return (
    <DomainList.List
      batchSize={5}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <DomainList.StaticData data={domains} />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
        matcher={(filterValue, propertyValue) =>
          filterValue === "Type Domain"
            ? propertyValue === "Domain"
            : propertyValue === "Subdomain"
        }
        values={["Type Domain", "Type Subdomain"]}
        defaultSelected={["Type Domain"]}
      />
      <DomainList.Filter
        property="verified"
        mode="some"
        name="Verifizierung"
        matcher={(filterValue, propertyValue) =>
          filterValue === "Verifizierung Verifiziert"
            ? propertyValue
            : !propertyValue
        }
        defaultSelected={["Verifizierung Unverifiziert"]}
        values={[
          "Verifizierung Verifiziert",
          "Verifizierung Unverifiziert",
        ]}
      />
      <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>
  );
}
```

---

# Behavior

## List im Modal

Wenn sich eine List im Modal öffnet, sollte das Suchfeld automatisch fokussiert
sein, um dem User eine direkte Eingabe zu ermöglichen. Setze dafür auf das
Suchfeld die Property `autoFocus`.

## Responsive layout

Auf kleinen Bildschirmgrößen passt sich der Header der List an. Ansicht und
Sortierung werden in einem Icon-[Button](/04-components/actions/button/overview)
zusammengefasst, auch die Filter werden unter einem Icon-Button versteckt. Auf
besonders kleinen Bildschirmen wandern diese Elemente zusammen mit der Suche
eine Zeile nach unten.

Der Inhalt der ListItems bricht in der Regel bei kleineren Bildschirmgrößen um.
Der Top Content (siehe Content Slots im Overview) kann mithilfe von
[ColumnLayouts](/04-components/structure/column-layout/overview) auf kleinen
Bildschirmen ausgeblendet werden. Dabei dürfen jedoch nur Informationen
ausgeblendet werden, die der User nicht benötigt, um das ListItem zu verstehen.

**ℹ️ Info**

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

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

  return (
    <DomainList.List
      batchSize={2}
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <DomainList.StaticData data={domains} />
      <ActionGroup>
        <Button
          color="secondary"
          variant="soft"
          slot="secondary"
        >
          <IconDownload />
        </Button>
        <Button color="accent">Anlegen</Button>
      </ActionGroup>
      <DomainList.Search />
      <DomainList.Filter
        property="type"
        mode="some"
        name="Typ"
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="asc"
        defaultEnabled
        directionName="aufsteigend"
      />
      <DomainList.Sorting
        property="hostname"
        name="Alphabetisch"
        direction="desc"
        directionName="absteigend"
      />
      <DomainList.Sorting
        property="type"
        name="Typ"
        direction="asc"
      />
      <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}
      >
        {(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>
  );
}
```

---

# Accessibility

Wird eine List als einziger Hauptinhalt auf einer Seite verwendet, ist keine
zusätzliche [Heading](/04-components/content/heading) erforderlich. In diesem
Fall muss die List über ein `aria-label` beschrieben werden.

Hat die List hingegen eine eigene Heading, muss diese mit `aria-labelledby` der
List zugeordnet werden.

