diff --git a/packages/components/src/components/List/components/Items/components/Item/hooks/useGridItemProps.tsx b/packages/components/src/components/List/components/Items/components/Item/hooks/useGridItemProps.tsx
new file mode 100644
index 000000000..04354995d
--- /dev/null
+++ b/packages/components/src/components/List/components/Items/components/Item/hooks/useGridItemProps.tsx
@@ -0,0 +1,79 @@
+import type { PropsWithChildren } from "react";
+import React, { useEffect, useId, useRef, useState } from "react";
+import { useList } from "@/components/List";
+import type { PropsContext } from "@/lib/propsContext";
+import { dynamic, PropsContextProvider } from "@/lib/propsContext";
+import { AccordionButton } from "@/components/List/components/Items/components/Item/components/AccordionButton";
+
+interface P extends PropsWithChildren {
+ data: never;
+}
+
+export const useGridItemProps = (props: P) => {
+ const { data, children: childrenFromProps } = props;
+ const list = useList();
+ const itemView = list.itemView;
+ const onAction = list.onAction;
+
+ const [isExpanded, setIsExpanded] = useState(
+ itemView?.defaultExpanded?.(data) ?? false,
+ );
+ const contentElementId = useId();
+ const itemRef = useRef
(null);
+
+ const accordion = list.accordion;
+ const children = childrenFromProps ?? itemView?.render(data);
+
+ useEffect(() => {
+ if (accordion) {
+ itemRef.current?.setAttribute("aria-expanded", String(isExpanded));
+ itemRef.current?.setAttribute("aria-controls", contentElementId);
+ }
+ }, [isExpanded, contentElementId, itemRef.current, accordion]);
+
+ if (!accordion) {
+ return {
+ gridItemProps: {
+ onAction: () => {
+ onAction?.(data);
+ },
+ },
+ children,
+ };
+ }
+
+ const toggleAccordion = () => {
+ setIsExpanded((current) => !current);
+ onAction?.(data);
+ };
+
+ const propsContext: PropsContext = {
+ Content: {
+ id: dynamic((p) => (p.slot === "bottom" ? contentElementId : undefined)),
+ wrapWith: dynamic((p) =>
+ p.slot === "bottom" ? (
+
+ ) : undefined,
+ ),
+ },
+ };
+
+ return {
+ gridItemProps: {
+ ref: itemRef,
+ onAction: toggleAccordion,
+ },
+ children: (
+
+ {children}
+
+ ),
+ };
+};
diff --git a/packages/components/src/components/List/locales/de-DE.locale.json b/packages/components/src/components/List/locales/de-DE.locale.json
index 72a7677f7..6a4f7f5af 100644
--- a/packages/components/src/components/List/locales/de-DE.locale.json
+++ b/packages/components/src/components/List/locales/de-DE.locale.json
@@ -11,5 +11,7 @@
"list.settings.viewMode.list": "Liste",
"list.settings.viewMode.table": "Tabelle",
"list.showMore": "Mehr anzeigen",
- "list.sorting": "Sortierung"
+ "list.sorting": "Sortierung",
+ "list.toggleExpandButton.collapse": "Weniger anzeigen",
+ "list.toggleExpandButton.expand": "Mehr anzeigen"
}
diff --git a/packages/components/src/components/List/locales/en-EN.locale.json b/packages/components/src/components/List/locales/en-EN.locale.json
index 9b36f8e0f..0220f90f4 100644
--- a/packages/components/src/components/List/locales/en-EN.locale.json
+++ b/packages/components/src/components/List/locales/en-EN.locale.json
@@ -11,5 +11,7 @@
"list.settings.viewMode.list": "List",
"list.settings.viewMode.table": "Table",
"list.showMore": "Show more",
- "list.sorting": "Sorting"
+ "list.sorting": "Sorting",
+ "list.toggleExpandButton.collapse": "Show less",
+ "list.toggleExpandButton.expand": "Show more"
}
diff --git a/packages/components/src/components/List/model/List.ts b/packages/components/src/components/List/model/List.ts
index 06a2645e5..acc59c203 100644
--- a/packages/components/src/components/List/model/List.ts
+++ b/packages/components/src/components/List/model/List.ts
@@ -31,6 +31,7 @@ export class List {
public readonly batches: BatchesController;
public readonly loader: IncrementalLoader;
public readonly onAction?: ItemActionFn;
+ public readonly accordion: boolean;
public readonly getItemId?: GetItemId;
public readonly componentProps: ListSupportedComponentProps;
public viewMode: ListViewMode;
@@ -60,6 +61,7 @@ export class List {
onAction,
getItemId,
defaultViewMode,
+ accordion = false,
...componentProps
} = shape;
@@ -78,6 +80,7 @@ export class List {
this.sorting = sorting.map((shape) => new Sorting(this, shape));
this.search = search ? new Search(this, search) : undefined;
this.itemView = itemView ? new ItemView(this, itemView) : undefined;
+ this.accordion = accordion;
this.table = table ? new Table(this, table) : undefined;
this.batches = new BatchesController(this, batchesController);
this.componentProps = componentProps;
diff --git a/packages/components/src/components/List/model/item/ItemView.ts b/packages/components/src/components/List/model/item/ItemView.ts
index 499078177..fdc932a1f 100644
--- a/packages/components/src/components/List/model/item/ItemView.ts
+++ b/packages/components/src/components/List/model/item/ItemView.ts
@@ -6,6 +6,7 @@ import type List from "@/components/List/model/List";
export interface ItemViewShape {
textValue?: (data: T) => string;
href?: (data: T) => string;
+ defaultExpanded?: (data: T) => boolean;
renderFn?: RenderItemFn;
fallback?: ReactElement;
}
@@ -14,15 +15,17 @@ export class ItemView {
public readonly list: List;
public readonly textValue?: (data: T) => string;
public readonly href?: (data: T) => string;
+ public readonly defaultExpanded?: (data: T) => boolean;
public readonly fallback?: ReactElement;
private readonly renderFn?: RenderItemFn;
public constructor(list: List, shape: ItemViewShape = {}) {
- const { fallback, textValue, href, renderFn } = shape;
+ const { fallback, textValue, href, defaultExpanded, renderFn } = shape;
this.list = list;
this.textValue = textValue;
this.renderFn = renderFn;
this.href = href;
+ this.defaultExpanded = defaultExpanded;
this.fallback = fallback;
}
diff --git a/packages/components/src/components/List/model/types.ts b/packages/components/src/components/List/model/types.ts
index 41f0ab7cf..a9a668645 100644
--- a/packages/components/src/components/List/model/types.ts
+++ b/packages/components/src/components/List/model/types.ts
@@ -42,6 +42,7 @@ export interface ListShape extends ListSupportedComponentProps {
table?: TableShape;
onAction?: ItemActionFn;
+ accordion?: boolean;
getItemId?: GetItemId;
onChange?: OnListChanged;
defaultViewMode?: ListViewMode;
diff --git a/packages/components/src/components/List/stories/Default.stories.tsx b/packages/components/src/components/List/stories/Default.stories.tsx
index 7829c0821..67be8742a 100644
--- a/packages/components/src/components/List/stories/Default.stories.tsx
+++ b/packages/components/src/components/List/stories/Default.stories.tsx
@@ -16,6 +16,7 @@ import { ListItemView, ListSummary, typedList } from "@/components/List";
import { Button } from "@/components/Button";
import IconDownload from "@/components/Icon/components/icons/IconDownload";
import { ActionGroup } from "@/components/ActionGroup";
+import { Content } from "@/components/Content";
const loadDomains: AsyncDataLoader = async (opts) => {
const response = await getDomains({
@@ -178,3 +179,42 @@ export const WithSummary: Story = {
);
},
};
+
+export const WithAccordion: Story = {
+ render: () => {
+ const InvoiceList = typedList<{
+ id: string;
+ date: string;
+ amount: string;
+ }>();
+
+ return (
+
+ Invoices
+
+
+ invoice.id === "RG100001"}
+ >
+ {(invoice) => (
+
+ {invoice.id}
+
+
+ {invoice.date} - {invoice.amount}
+
+
+
+ )}
+
+
+
+ );
+ },
+};
diff --git a/packages/docs/src/content/03-components/structure/list/examples/accordion.tsx b/packages/docs/src/content/03-components/structure/list/examples/accordion.tsx
new file mode 100644
index 000000000..086298649
--- /dev/null
+++ b/packages/docs/src/content/03-components/structure/list/examples/accordion.tsx
@@ -0,0 +1,48 @@
+import {
+ List,
+ ListItem,
+ ListItemView,
+ ListStaticData,
+} from "@mittwald/flow-react-components/List";
+import {
+ type Domain,
+ domains,
+} from "@/content/03-components/structure/list/examples/domainApi";
+import Avatar from "@mittwald/flow-react-components/Avatar";
+import Heading from "@mittwald/flow-react-components/Heading";
+import Text from "@mittwald/flow-react-components/Text";
+import {
+ IconDomain,
+ IconSubdomain,
+} from "@mittwald/flow-react-components/Icons";
+import AlertBadge from "@mittwald/flow-react-components/AlertBadge";
+import Content from "@mittwald/flow-react-components/Content";
+
+
+
+ >
+ {(domain) => (
+
+
+ {domain.type === "Domain" ? (
+
+ ) : (
+
+ )}
+
+
+ {domain.hostname}
+ {!domain.verified && (
+
+ Unverifiziert
+
+ )}
+
+ {domain.type}
+ Mehr Inhalt
+
+ )}
+
+
;
diff --git a/packages/docs/src/content/03-components/structure/list/overview.mdx b/packages/docs/src/content/03-components/structure/list/overview.mdx
index 07a3c1899..3cd8e3ff9 100644
--- a/packages/docs/src/content/03-components/structure/list/overview.mdx
+++ b/packages/docs/src/content/03-components/structure/list/overview.mdx
@@ -60,3 +60,11 @@ Verwende eine `` um eine Zusammenfassung, wie beispielsweise die
Summe der Beträge, anzuzeigen.
+
+## Mit Accordion
+
+Aktiviere das Accordion-Verhalten über die `accordion` Property. So lässt sich
+ein Listen-Element durch Klick ein- bzw. ausklappen. Der erweiterte Inhalt muss
+dann in `` enthalten sein.
+
+