# ListItemView

List.ItemView ist die vorgefertigte Layout-Lösung für ein List-Item.

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

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

  return (
    <DomainList.List
      batchSize={3}
      hidePagination
      aria-label="Domains"
      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}</Heading>
            <Text>{domain.type}</Text>

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

`List.ItemView` ist die vorgefertigte Layout-Lösung für ein List-Item und ordnet
Avatar, Titel, Content und Aktionen einheitlich an.

---

# Best Practices

- Halte den Titel kurz und eindeutig. Lange Headings brechen um und erschweren
  das schnelle Scannen der List.
- Zeige nur die Informationen, die zum Verständnis des Elements nötig sind. Zu
  viele Details machen die List unübersichtlich.
- Ergänze im Untertitel weitere Informationen nach dem Muster „Beschreibung – 1.
  Information – 2. Information“.
- Verlagere selten benötigte Details in Top oder Bottom Content oder blende sie
  über [ColumnLayout](https://flow.mittwald.de/components/structure/column-layout) auf kleinen
  Bildschirmen aus.
- Gib jedem List-Item über `textValue` seinen Text mit. Erst damit findet der
  User es über die Tastatur, indem er dessen Anfang tippt.

---

# Als Raster

In der Rasteransicht wird das List-Item kompakter dargestellt: Der Avatar wird
größer und eckig, Top Content sowie die Accordion-Funktion entfallen. Aktiviere
die Darstellung über `showTiles` an `List.Item`.

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

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

  return (
    <DomainList.List
      batchSize={3}
      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}</Heading>
            <Text>{domain.type}</Text>

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

---

# Mit Avatar

Der [Avatar](https://flow.mittwald.de/components/content/avatar) steht am Anfang eines List-Items und
spiegelt das Element wider: Ein [Icon](https://flow.mittwald.de/components/content/icon) steht für die
Kategorie (z. B. ein Domain-Icon), ein hochgeladenes
[Image](https://flow.mittwald.de/components/content/image) wird angezeigt, andernfalls erscheinen
Initialen. Elemente derselben Kategorie erhalten dasselbe Icon.

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

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

  return (
    <DomainList.List
      batchSize={3}
      hidePagination
      aria-label="Domains"
      getItemId={(domain) => domain.id}
    >
      <DomainList.StaticData
        data={domains.filter(
          (domain) => domain.type === "Domain",
        )}
      />
      <DomainList.Item
        textValue={(domain) => domain.domain}
      >
        {(domain) => (
          <DomainList.ItemView>
            <Avatar color="blue">
              <IconDomain />
            </Avatar>
            <Heading>{domain.hostname}</Heading>
            <Text>{domain.type}</Text>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}
```

---

# Mit Link

Setze `href` an `List.Item`, um das gesamte List-Item zu verlinken.

```tsx
import {
  Avatar,
  ContextMenu,
  Heading,
  IconDomain,
  MenuItem,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/components/list/list-item-view/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

Aktiviere `accordion` an der `List`, damit sich ein List-Item per Klick ein-
oder ausklappen lässt. Den erweiterten Inhalt platzierst du in der ItemView über
`<Content slot="bottom" />`.

```tsx
import {
  Avatar,
  Content,
  Heading,
  IconDomain,
  ListItemView,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/components/list/list-item-view/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](https://flow.mittwald.de/components/form-controls/checkbox) in einem List-Item werden
automatisch am Anfang der Zeile angeordnet. Ihre Funktionalität wird nicht von
der List gesteuert und muss individuell implementiert werden. Achte darauf, dass
die gesamte Zeile zur Auswahl 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/components/list/list-item-view/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 List-Item 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/components/list/list-item-view/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

Der `List.ItemView` können die
[ColumnLayout](https://flow.mittwald.de/components/structure/column-layout)-Properties `s`, `m` und `l`
mitgegeben werden, um Seitenverhältnis und 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 Spalten auch `null` gesetzt werden kann, lässt sich nicht zwingend
benötigter Content in kleineren Ansichten ausblenden. 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 ContextMenu

Aktionen eines List-Items platzierst du in einem
[ContextMenu](https://flow.mittwald.de/components/actions/context-menu). Für dauerhaft sichtbare
Aktionen nutzt du stattdessen [Buttons](https://flow.mittwald.de/components/actions/button) in einer
ActionGroup.

```tsx
import {
  Avatar,
  ContextMenu,
  Heading,
  IconDelete,
  IconDomain,
  IconEdit,
  IconInfo,
  IconSubdomain,
  MenuItem,
  Text,
  typedList,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/components/list/list-item-view/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} />
      <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}</Heading>
            <Text>{domain.type}</Text>

            <ContextMenu>
              <MenuItem>
                <IconInfo />
                <Text>Details anzeigen</Text>
              </MenuItem>
              <MenuItem>
                <IconEdit />
                <Text>Bearbeiten</Text>
              </MenuItem>
              <MenuItem>
                <IconDelete />
                <Text>Löschen</Text>
              </MenuItem>
            </ContextMenu>
          </DomainList.ItemView>
        )}
      </DomainList.Item>
    </DomainList.List>
  );
}
```

---

# Mit ActionGroup

Verwende eine ActionGroup innerhalb des `<Content />`, um
[Buttons](https://flow.mittwald.de/components/actions/button) im List-Item zu platzieren.

```tsx
import {
  ActionGroup,
  Avatar,
  Button,
  Content,
  Heading,
  IconDomain,
  typedList,
  IconEdit,
  IconDelete,
} from "@mittwald/flow-react-components";
import {
  type Domain,
  domains,
} from "@/content/components/list/list-item-view/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>
  );
}
```

