Skip to content

Commit

Permalink
feat(list): add tile view
Browse files Browse the repository at this point in the history
  • Loading branch information
Lisa18289 committed Jan 7, 2025
1 parent 28e4c8f commit c2c6200
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@
&[data-empty="true"] {
border: none;
}

&.tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
border: none;
gap: var(--list-item--spacing);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export const Items: FC = () => {
<Item key={item.id} data={item.data} id={item.id} />
));

const rootClassName = clsx(styles.items, isLoading && styles.isLoading);
const rootClassName = clsx(
styles.items,
isLoading && styles.isLoading,
list.tile && styles.tiles,
);

return (
<div aria-hidden={isInitiallyLoading} aria-busy={isLoading}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,27 @@
background-color: var(--list-item--background-color--pressed);
}
}

&.tile {
border-color: var(--list-item--border-color);
border-width: var(--list-item--border-width);
border-style: var(--list-item--border-style);
border-radius: var(--list-item--corner-radius);

&:where(.hasAction) {
&:not(.isSelected) {
&:hover {
img {
filter: brightness(var(--image-button--brightness--hover));
}
}
}

&[data-pressed] {
img {
filter: brightness(var(--image-button--brightness--pressed));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const Item = (props: Props) => {
styles.item,
hasAction && styles.hasAction,
props.isSelected && styles.isSelected,
list.tile && styles.tile,
)
}
textValue={textValue}
Expand All @@ -48,10 +49,21 @@ export const Item = (props: Props) => {
);
};

export const ItemContainer: FC<PropsWithChildren> = (props) => (
<Aria.GridListItem textValue="-" className={styles.item}>
{props.children}
</Aria.GridListItem>
);
export const ItemContainer: FC<PropsWithChildren> = (props) => {
const list = useList();

return (
<Aria.GridListItem
textValue="-"
className={clsx(
styles.item,
styles.fallbackItem,
list.tile && styles.tile,
)}
>
{props.children}
</Aria.GridListItem>
);
};

export default Item;
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@ import { Heading } from "@/components/Heading";
import { Text } from "@/components/Text";
import View from "@/components/List/components/Items/components/Item/components/View/View";
import SkeletonText from "@/components/SkeletonText";
import { useList } from "@/components/List";
import { Skeleton } from "@/components/Skeleton";

export const SkeletonView: FC = () => (
<View>
<Heading>
<SkeletonText width="200px" />
</Heading>
<Text>
<SkeletonText width="300px" />
</Text>
</View>
);
export const SkeletonView: FC = () => {
const list = useList();

return (
<View>
{list.tile && <Skeleton height="200px" />}
<Heading>
<SkeletonText width="200px" />
</Heading>
<Text>
<SkeletonText width="300px" />
</Text>
</View>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,24 @@
order: 4;
width: 100%;
}

&.tile {
padding: 0;

.image {
width: 100%;
border-color: var(--list-item--border-color);
border-width: var(--list-item--border-width);
border-style: var(--list-item--border-style);
}

.tileContent {
display: flex;
justify-content: space-between;
align-items: start;
padding-inline: var(--list-item--spacing);
padding-bottom: var(--list-item--spacing);
width: 100%;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { OptionsButton } from "@/components/List/components/Items/components/Ite
import { TunnelExit, TunnelProvider } from "@mittwald/react-tunnel";
import type { PropsWithClassName } from "@/lib/types/props";
import clsx from "clsx";
import { useList } from "@/components/List";
import { Wrap } from "@/components/Wrap";

type Props = PropsWithChildren & PropsWithClassName;

Expand All @@ -19,6 +21,7 @@ const getStyleForContentSlot = (slot?: string) =>

export const View = (props: Props) => {
const { children, className } = props;
const list = useList();

const propsContext: PropsContext = {
ContextMenu: {
Expand All @@ -45,6 +48,9 @@ export const View = (props: Props) => {
className: styles.avatar,
tunnelId: "title",
},
Image: {
className: styles.image,
},
Heading: {
className: styles.heading,
level: 5,
Expand All @@ -59,22 +65,27 @@ export const View = (props: Props) => {
},
};

const rootClassName = clsx(styles.view, className);
const rootClassName = clsx(styles.view, list.tile && styles.tile, className);

return (
<div className={rootClassName}>
<PropsContextProvider props={propsContext} mergeInParentContext>
<TunnelProvider>
<div className={styles.content}>
{children}
<div className={styles.title}>
<TunnelExit id="title" />
<div className={styles.subTitle}>
<TunnelExit id="text" />
<Wrap if={list.tile}>
<div className={styles.tileContent}>
<div className={styles.title}>
<TunnelExit id="title" />
<div className={styles.subTitle}>
<TunnelExit id="text" />
</div>
</div>
{list.tile && <TunnelExit id="button" />}
</div>
</div>
</Wrap>
</div>
<TunnelExit id="button" />
{!list.tile && <TunnelExit id="button" />}
</TunnelProvider>
</PropsContextProvider>
</div>
Expand Down
13 changes: 7 additions & 6 deletions packages/components/src/components/List/model/List.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import type { SettingsStore } from "@/components/SettingsProvider/models/Setting
import z from "zod";

export class List<T> {
public static readonly viewModeSettingsStorageSchema = z
.enum(["list", "table"])
.optional();
public readonly filters: Filter<T, never, never>[];
public readonly itemView?: ItemView<T>;
public readonly table?: Table<T>;
Expand All @@ -32,21 +35,17 @@ export class List<T> {
public readonly loader: IncrementalLoader<T>;
public readonly onAction?: ItemActionFn<T>;
public readonly accordion: boolean;
public readonly tile: boolean;
public readonly getItemId?: GetItemId<T>;
public readonly componentProps: ListSupportedComponentProps;
public viewMode: ListViewMode;
public readonly setViewMode: (viewMode: ListViewMode) => void;

private readonly settingsStore?: SettingsStore;
public readonly supportsSettingsStorage: boolean;
public readonly settingStorageKey?: string;
private readonly settingsStore?: SettingsStore;
private readonly viewModeStorageKey?: string;
private readonly filterSettingsStorageKey?: string;

public static readonly viewModeSettingsStorageSchema = z
.enum(["list", "table"])
.optional();

public constructor(shape: ListShape<T>) {
const {
settingStorageKey,
Expand All @@ -62,6 +61,7 @@ export class List<T> {
getItemId,
defaultViewMode,
accordion = false,
tile = false,
...componentProps
} = shape;

Expand All @@ -81,6 +81,7 @@ export class List<T> {
this.search = search ? new Search(this, search) : undefined;
this.itemView = itemView ? new ItemView(this, itemView) : undefined;
this.accordion = accordion;
this.tile = tile;
this.table = table ? new Table(this, table) : undefined;
this.batches = new BatchesController(this, batchesController);
this.componentProps = componentProps;
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/components/List/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ListShape<T> extends ListSupportedComponentProps {

onAction?: ItemActionFn<T>;
accordion?: boolean;
tile?: boolean;
getItemId?: GetItemId<T>;
onChange?: OnListChanged<T>;
defaultViewMode?: ListViewMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { Button } from "@/components/Button";
import IconDownload from "@/components/Icon/components/icons/IconDownload";
import { ActionGroup } from "@/components/ActionGroup";
import { Content } from "@/components/Content";
import { Image } from "@/components/Image";
import { dummyText } from "@/lib/dev/dummyText";

const loadDomains: AsyncDataLoader<Domain> = async (opts) => {
const response = await getDomains({
Expand Down Expand Up @@ -218,3 +220,56 @@ export const WithAccordion: Story = {
);
},
};

export const WithTile: Story = {
render: () => {
const DomainList = typedList<Domain>();
const availableTypes = usePromise(getTypes, []);

return (
<Section>
<Heading>Domains</Heading>
<DomainList.List
batchSize={5}
aria-label="Domains"
onAction={(domain) => console.log(domain.hostname)}
tile
>
<DomainList.LoaderAsync manualPagination manualSorting={false}>
{loadDomains}
</DomainList.LoaderAsync>
<DomainList.Filter
values={availableTypes}
property="type"
mode="all"
name="Typ"
defaultSelected={["Domain"]}
/>
<DomainList.Search autoFocus autoSubmit />
<DomainList.Sorting property="domain" name="A-Z" />
<DomainList.Sorting property="domain" name="Z-A" direction="desc" />

<DomainList.Item textValue={(domain) => domain.hostname}>
{(domain) => (
<DomainList.ItemView>
<Image src={dummyText.imageSrc} />
<Heading>
{domain.hostname}
{!domain.verified && (
<AlertBadge status="warning">Not verified</AlertBadge>
)}
</Heading>
<Text>{domain.type}</Text>

<ContextMenu>
<MenuItem>Show details</MenuItem>
<MenuItem>Delete</MenuItem>
</ContextMenu>
</DomainList.ItemView>
)}
</DomainList.Item>
</DomainList.List>
</Section>
);
},
};

0 comments on commit c2c6200

Please sign in to comment.