Skip to content

Commit 2058342

Browse files
authored
feat(List): add tile view
1 parent 1debec2 commit 2058342

File tree

21 files changed

+397
-168
lines changed

21 files changed

+397
-168
lines changed

packages/components/src/components/ContextMenu/lib.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,5 @@ export const getMenuItemSelectionVariant = (
2525
export const getCloseOverlayType = (
2626
selectionMode?: ContextMenuSelectionMode,
2727
) => {
28-
return selectionMode === "single" || selectionMode === "navigation"
29-
? "ContextMenu"
30-
: undefined;
28+
return selectionMode === "multiple" ? undefined : "ContextMenu";
3129
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export const List = flowComponent("List", (props) => {
179179
<TunnelExit id="listSummary" />
180180
)}
181181
{listModel.viewMode === "list" && <Items />}
182+
{listModel.viewMode === "tiles" && <Items tiles />}
182183
{listModel.viewMode === "table" && <Table />}
183184
</div>
184185
<Footer />

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const Header: FC<Props> = (props) => {
2424
list.visibleSorting.length === 0 &&
2525
!list.search &&
2626
!list.table &&
27+
!(list.itemView?.showTiles && list.itemView?.showList) &&
2728
!hasActionGroup
2829
) {
2930
return null;

packages/components/src/components/List/components/Header/components/ViewModeMenu/ViewModeMenu.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ export const ViewModeMenu: FC = () => {
1818
const selectedViewMode = list.viewMode;
1919

2020
const availableViewModes: ListViewMode[] = [];
21-
if (list.itemView) {
21+
if (list.itemView?.showList) {
2222
availableViewModes.push("list");
2323
}
2424
if (list.table) {
2525
availableViewModes.push("table");
2626
}
27+
if (list.itemView?.showTiles) {
28+
availableViewModes.push("tiles");
29+
}
2730

2831
if (availableViewModes.length <= 1) {
2932
return null;

packages/components/src/components/List/components/Items/Items.module.css renamed to packages/components/src/components/List/components/Items/Items.module.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@
1919
&[data-empty="true"] {
2020
border: none;
2121
}
22+
23+
&.tiles {
24+
display: grid;
25+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
26+
border: none;
27+
gap: var(--list-item--spacing);
28+
}
2229
}

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import type { FC } from "react";
22
import React from "react";
33
import { useList } from "@/components/List/hooks/useList";
4-
import styles from "./Items.module.css";
4+
import styles from "./Items.module.scss";
55
import clsx from "clsx";
66
import * as Aria from "react-aria-components";
77
import Item from "@/components/List/components/Items/components/Item/Item";
88
import { EmptyView } from "@/components/List/components/EmptyView/EmptyView";
99
import { FallbackItems } from "@/components/List/components/Items/components/FallbackItems/FallbackItems";
1010

11-
export const Items: FC = () => {
11+
interface Props {
12+
tiles?: boolean;
13+
}
14+
15+
export const Items: FC<Props> = (props) => {
16+
const { tiles } = props;
17+
1218
const list = useList();
1319
const isLoading = list.loader.useIsLoading();
1420
const isInitiallyLoading = list.loader.useIsInitiallyLoading();
@@ -18,10 +24,14 @@ export const Items: FC = () => {
1824
}
1925

2026
const rows = list.items.entries.map((item) => (
21-
<Item key={item.id} data={item.data} id={item.id} />
27+
<Item key={item.id} data={item.data} id={item.id} tiles={tiles} />
2228
));
2329

24-
const rootClassName = clsx(styles.items, isLoading && styles.isLoading);
30+
const rootClassName = clsx(
31+
styles.items,
32+
isLoading && styles.isLoading,
33+
tiles && styles.tiles,
34+
);
2535

2636
return (
2737
<div aria-hidden={isInitiallyLoading} aria-busy={isLoading}>

packages/components/src/components/List/components/Items/components/Item/Item.module.scss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,27 @@
3131
background-color: var(--list-item--background-color--pressed);
3232
}
3333
}
34+
35+
&.tile {
36+
border-color: var(--list-item--border-color);
37+
border-width: var(--list-item--border-width);
38+
border-style: var(--list-item--border-style);
39+
border-radius: var(--list-item--corner-radius);
40+
41+
&:where(.hasAction) {
42+
&:not(.isSelected) {
43+
&:hover {
44+
:global(.flow--avatar) {
45+
filter: brightness(var(--image-button--brightness--hover));
46+
}
47+
}
48+
}
49+
50+
&[data-pressed] {
51+
:global(.flow--avatar) {
52+
filter: brightness(var(--image-button--brightness--pressed));
53+
}
54+
}
55+
}
56+
}
3457
}

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { useGridItemProps } from "@/components/List/components/Items/components/
1111
interface Props extends PropsWithChildren {
1212
id: Key;
1313
data: never;
14+
tiles?: boolean;
1415
}
1516

1617
export const Item = (props: Props) => {
17-
const { id, data } = props;
18+
const { id, data, tiles } = props;
1819
const list = useList();
1920

2021
const itemView = list.itemView;
@@ -37,6 +38,7 @@ export const Item = (props: Props) => {
3738
styles.item,
3839
hasAction && styles.hasAction,
3940
props.isSelected && styles.isSelected,
41+
tiles && styles.tile,
4042
)
4143
}
4244
textValue={textValue}
@@ -48,10 +50,19 @@ export const Item = (props: Props) => {
4850
);
4951
};
5052

51-
export const ItemContainer: FC<PropsWithChildren> = (props) => (
52-
<Aria.GridListItem textValue="-" className={styles.item}>
53-
{props.children}
54-
</Aria.GridListItem>
55-
);
53+
export const ItemContainer: FC<PropsWithChildren & { tiles?: boolean }> = (
54+
props,
55+
) => {
56+
const { tiles, children } = props;
57+
58+
return (
59+
<Aria.GridListItem
60+
textValue="-"
61+
className={clsx(styles.item, styles.fallbackItem, tiles && styles.tile)}
62+
>
63+
{children}
64+
</Aria.GridListItem>
65+
);
66+
};
5667

5768
export default Item;

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@ import { Heading } from "@/components/Heading";
44
import { Text } from "@/components/Text";
55
import View from "@/components/List/components/Items/components/Item/components/View/View";
66
import SkeletonText from "@/components/SkeletonText";
7+
import { useList } from "@/components/List";
8+
import { Skeleton } from "@/components/Skeleton";
79

8-
export const SkeletonView: FC = () => (
9-
<View>
10-
<Heading>
11-
<SkeletonText width="200px" />
12-
</Heading>
13-
<Text>
14-
<SkeletonText width="300px" />
15-
</Text>
16-
</View>
17-
);
10+
export const SkeletonView: FC = () => {
11+
const list = useList();
12+
13+
const showTiles = list.viewMode === "tiles";
14+
15+
return (
16+
<View>
17+
{showTiles && <Skeleton style={{ aspectRatio: 16 / 9 }} />}
18+
<Heading>
19+
<SkeletonText width="200px" />
20+
</Heading>
21+
<Text>
22+
<SkeletonText width="300px" />
23+
</Text>
24+
</View>
25+
);
26+
};

packages/components/src/components/List/components/Items/components/Item/components/View/View.module.scss

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
display: flex;
55
padding: var(--list-item--padding);
66
gap: var(--list-item--spacing);
7-
align-items: start;
7+
align-items: center;
8+
flex-wrap: wrap;
89

910
.content {
1011
display: flex;
@@ -24,7 +25,8 @@
2425
"avatar subtitle";
2526
grid-template-columns: auto 1fr;
2627

27-
&:empty {
28+
&:empty,
29+
.subTitle:empty {
2830
display: none;
2931
}
3032

@@ -35,7 +37,7 @@
3537
grid-template-columns: 1fr;
3638
}
3739

38-
&:not(:has(.subTitle)) {
40+
&:has(.subTitle:empty) {
3941
grid-template-areas: "avatar title";
4042

4143
&:not(:has(.avatar)) {
@@ -77,4 +79,51 @@
7779
order: 4;
7880
width: 100%;
7981
}
82+
83+
&.tile {
84+
padding: 0;
85+
86+
.action {
87+
display: none;
88+
}
89+
90+
.avatarContainer {
91+
width: 100%;
92+
overflow: hidden;
93+
border-bottom-color: var(--list-item--border-color);
94+
border-bottom-width: var(--list-item--border-width);
95+
border-bottom-style: var(--list-item--border-style);
96+
border-top-left-radius: var(--corner-radius--default);
97+
border-top-right-radius: var(--corner-radius--default);
98+
aspect-ratio: 16 / 9;
99+
}
100+
101+
.avatar {
102+
width: 100%;
103+
height: 100%;
104+
border-radius: 0;
105+
106+
:global(.flow--avatar--icon) {
107+
width: var(--size-px--xxl);
108+
height: var(--size-px--xxl);
109+
}
110+
111+
:global(.flow--avatar--initials) {
112+
font-size: var(--size-px--xxl);
113+
}
114+
}
115+
116+
.content {
117+
display: flex;
118+
justify-content: space-between;
119+
align-items: start;
120+
padding-inline: var(--list-item--spacing);
121+
padding-bottom: var(--list-item--spacing);
122+
width: 100%;
123+
}
124+
125+
.topContent {
126+
width: 100%;
127+
}
128+
}
80129
}

0 commit comments

Comments
 (0)