Skip to content

Commit ca7bb02

Browse files
committed
feat(List): support custom empty views
1 parent 2d47892 commit ca7bb02

333 files changed

Lines changed: 263 additions & 19 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/components/src/components/List/List.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { type PropsContext, PropsContextProvider } from "@/lib/propsContext";
2727
import { deepFilterByType, deepFindOfType } from "@/lib/react/deepFindOfType";
2828
import DivView from "@/views/DivView";
2929
import { TunnelExit, TunnelProvider } from "@mittwald/react-tunnel";
30-
import type { PropsWithChildren } from "react";
30+
import type { PropsWithChildren, ReactNode } from "react";
3131
import Footer from "./components/Footer";
3232
import styles from "./List.module.css";
3333
import { listContext } from "./listContext";
@@ -50,6 +50,8 @@ export interface ListProps<T, TMeta = unknown>
5050
/** The number of items to be displayed on one page. */
5151
batchSize?: number;
5252
hidePagination?: boolean;
53+
emptySearchResultView?: ReactNode;
54+
emptyView?: ReactNode;
5355
}
5456

5557
export const List = flowComponent("List", (props) => {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { FC } from "react";
2+
import { IconSearch } from "@/components/Icon/components/icons";
3+
import locales from "../../locales/*.locale.json";
4+
import { useLocalizedStringFormatter } from "react-aria";
5+
import IllustratedMessageView from "@/views/IllustratedMessageView";
6+
import HeadingView from "@/views/HeadingView";
7+
import TextView from "@/views/TextView";
8+
9+
export type EmptySearchResultViewProps = Record<string, never>;
10+
11+
export const EmptySearchResultView: FC<EmptySearchResultViewProps> = () => {
12+
const stringFormatter = useLocalizedStringFormatter(locales);
13+
14+
return (
15+
<IllustratedMessageView>
16+
<IconSearch />
17+
<HeadingView>
18+
{stringFormatter.format("list.noResult.heading")}
19+
</HeadingView>
20+
<TextView>{stringFormatter.format("list.noResult.text")}</TextView>
21+
</IllustratedMessageView>
22+
);
23+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./EmptySearchResultView";
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { FC } from "react";
2+
import locales from "../../locales/*.locale.json";
3+
import { useLocalizedStringFormatter } from "react-aria";
4+
import IllustratedMessageView from "@/views/IllustratedMessageView";
5+
import HeadingView from "@/views/HeadingView";
6+
import TextView from "@/views/TextView";
7+
import { IconClose } from "@/components/Icon/components/icons";
8+
9+
export type EmptyViewProps = Record<string, never>;
10+
11+
export const EmptyView: FC<EmptyViewProps> = () => {
12+
const stringFormatter = useLocalizedStringFormatter(locales);
13+
14+
return (
15+
<IllustratedMessageView>
16+
<IconClose />
17+
<HeadingView>
18+
{stringFormatter.format("list.noItems.heading")}
19+
</HeadingView>
20+
<TextView>{stringFormatter.format("list.noItems.text")}</TextView>
21+
</IllustratedMessageView>
22+
);
23+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./EmptyView";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import DivView from "@/views/DivView";
2+
import type { FC, ReactNode } from "react";
3+
import styles from "../../components/Items/Items.module.scss";
4+
import { EmptyView } from "../EmptyView/EmptyView";
5+
import { EmptySearchResultView } from "../EmptySearchResultView";
6+
import type { EmptyViewType } from "../../model/types";
7+
8+
export interface EmptyViewContainerProps {
9+
emptyView?: ReactNode;
10+
emptySearchResultView?: ReactNode;
11+
viewType: EmptyViewType;
12+
}
13+
14+
/** @flr-generate all */
15+
export const EmptyViewContainer: FC<EmptyViewContainerProps> = (props) => {
16+
const emptyView = props.emptyView ?? <EmptyView />;
17+
const emptySearchResultView = props.emptySearchResultView ?? (
18+
<EmptySearchResultView />
19+
);
20+
return (
21+
<DivView className={styles.emptyView}>
22+
{props.viewType === "search" ? emptySearchResultView : emptyView}
23+
</DivView>
24+
);
25+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./view";
2+
export * from "./EmptyViewContainer";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* prettier-ignore */
2+
/* This file is auto-generated with the remote-components-generator */
3+
import type { EmptyViewContainer } from "./EmptyViewContainer";
4+
import type { ViewComponent } from "@/lib/viewComponentContext";
5+
6+
declare global {
7+
interface FlowViewComponents {
8+
EmptyViewContainer: ViewComponent<typeof EmptyViewContainer>;
9+
}
10+
}

packages/components/src/components/List/components/Items/Items.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EmptyView } from "@/components/List";
21
import Item from "@/components/List/components/Items/components/Item/Item";
32
import { useList } from "@/components/List/hooks/useList";
43
import DivView from "@/views/DivView";
@@ -7,6 +6,7 @@ import clsx from "clsx";
76
import type { FC } from "react";
87
import styles from "./Items.module.scss";
98
import { FallbackItems } from "./components/FallbackItems";
9+
import EmptyViewContainerView from "@/views/EmptyViewContainerView";
1010

1111
export const Items: FC = () => {
1212
const list = useList();
@@ -33,7 +33,13 @@ export const Items: FC = () => {
3333
<ItemsGridListView
3434
className={rootClassName}
3535
{...list.componentProps}
36-
renderEmptyState={() => <EmptyView />}
36+
emptyView={
37+
<EmptyViewContainerView
38+
viewType={list.getEmptyViewType()}
39+
emptySearchResultView={list.emptySearchResultView}
40+
emptyView={list.emptyView}
41+
/>
42+
}
3743
layout={tiles ? "grid" : "stack"}
3844
tileMaxWidth={list.itemView.tileMaxWidth}
3945
>

packages/components/src/components/List/components/Items/views/GridList/GridList.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import * as Aria from "react-aria-components";
2-
import type { FC } from "react";
3-
import { EmptyView } from "@/components/List/views/EmptyView";
2+
import type { FC, ReactNode } from "react";
43

54
export type GridListProps = Aria.GridListProps<never> & {
65
tileMaxWidth: number;
6+
emptyView?: ReactNode;
77
};
88

99
/** @flr-generate all */
1010
export const GridList: FC<GridListProps> = (props) => {
11-
const { tileMaxWidth, ...rest } = props;
11+
const { tileMaxWidth, emptyView, ...rest } = props;
1212

1313
return (
1414
<Aria.GridList
1515
{...rest}
16-
renderEmptyState={() => <EmptyView />}
16+
renderEmptyState={() => emptyView}
1717
style={{
1818
gridTemplateColumns: `repeat(auto-fill, minmax(${tileMaxWidth}px, 1fr))`,
1919
}}

0 commit comments

Comments
 (0)