Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin-ui): List and DataList components #4542

Open
wants to merge 23 commits into
base: feat/new-admin-ui
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aef97d5
wip: deprecate List component
leopuleo Feb 10, 2025
8c77b56
Merge branch 'feat/new-admin-ui' into leo/feat/ui-list
leopuleo Feb 10, 2025
ad8195f
wip: add missing props
leopuleo Feb 10, 2025
b05480c
wip: list
leopuleo Feb 10, 2025
a48ec83
wip: list
leopuleo Feb 12, 2025
d9c6c45
wip: list
leopuleo Feb 12, 2025
5d30aa7
refactor: ui list
leopuleo Feb 12, 2025
7e22b4c
refactor: ui list
leopuleo Feb 12, 2025
d645608
refactor: ui list
leopuleo Feb 12, 2025
0c4edaf
refactor: enhance List components and deprecate unused ones
leopuleo Feb 13, 2025
fd4dc44
refactor: update List components to use ListItemTextPrimary and impro…
leopuleo Feb 13, 2025
39d4fbd
refactor: update Loader component
leopuleo Feb 13, 2025
81a4fcb
refactor: update NoData component
leopuleo Feb 13, 2025
ef453be
refactor: update Heading and List components for improved structure a…
leopuleo Feb 13, 2025
103c4f9
refactor: update various components to improve structure and replace …
leopuleo Feb 13, 2025
fd6428b
refactor: simplify DataListModalOverlay and improve modal handling in…
leopuleo Feb 17, 2025
2b8ba55
refactor: enhance FormComponentProps and InputPrimitiveProps for bett…
leopuleo Feb 18, 2025
de86fca
refactor: reorganize DataList components and update exports
leopuleo Feb 18, 2025
823d98c
chore: remove rmwc deps
leopuleo Feb 18, 2025
c8d4907
chore: update deps
leopuleo Feb 18, 2025
fa20a99
refactor: update Button component styles for improved accessibility a…
leopuleo Feb 26, 2025
b2c80b5
Merge branch 'feat/new-admin-ui' into leo/feat/ui-list
leopuleo Feb 28, 2025
122240b
chore: merge
leopuleo Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/admin-ui/src/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { makeDecoratable, withStaticProps, cva, type VariantProps, cn } from "~/
import { AccordionRoot } from "./components/AccordionRoot";
import { AccordionItem, type AccordionItemProps } from "./components/AccordionItem";

const accordionVariants = cva("wby-group w-full", {
const accordionVariants = cva("wby-group wby-w-full", {
variants: {
variant: {
container: "wby-accordion-variant-container wby-gap-xs wby-flex wby-flex-col",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import * as React from "react";
import { makeDecoratable, withStaticProps } from "~/utils";
import { IconButton, IconButtonProps as IconButtonProps } from "~/Button";
import { AccordionItemSeparator } from "./AccordionItemSeparator";
import { useCallback } from "react";

type AccordionItemActionProps = IconButtonProps;

const AccordionItemActionBase = ({ onClick, ...props }: AccordionItemActionProps) => {
// We need to stop the event propagation to prevent the accordion from opening/closing when the action is clicked.
const onClickCallback = useCallback(
const onClickCallback = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as React from "react";
import { useCallback } from "react";
import { ReactComponent as DragHandleIcon } from "@material-design-icons/svg/filled/drag_indicator.svg";
import { makeDecoratable } from "~/utils";
import { Icon, IconProps as IconProps } from "~/Icon";
import { ReactComponent as DragHandleIcon } from "@material-design-icons/svg/filled/drag_indicator.svg";

interface AccordionItemHandleProps extends Omit<IconProps, "icon" | "label"> {
icon?: React.ReactElement;
Expand All @@ -11,7 +10,7 @@ interface AccordionItemHandleProps extends Omit<IconProps, "icon" | "label"> {

const AccordionItemHandleBase = ({ onClick, ...props }: AccordionItemHandleProps) => {
// We need to stop the event propagation to prevent the accordion from opening/closing when the handle is clicked.
const onClickCallback = useCallback(
const onClickCallback = React.useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from "react";
import { useMemo } from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ReactComponent as KeyboardArrowDownIcon } from "@material-design-icons/svg/outlined/keyboard_arrow_down.svg";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { cn } from "~/utils";
import { type AccordionItemProps } from "./AccordionItem";
import { AccordionItemAction } from "./AccordionItemAction";
Expand All @@ -16,7 +15,7 @@ const AccordionTrigger = ({ title, description, actions, icon, handle }: Accordi
// The following three attributes are required for the trigger to act as a button.
// We can't use the default button element here because the content of the trigger
// can also contain one or more buttons.
const divAsButtonProps = useMemo<React.HTMLAttributes<HTMLDivElement>>(() => {
const divAsButtonProps = React.useMemo<React.HTMLAttributes<HTMLDivElement>>(() => {
return {
role: "button",
tabIndex: 0,
Expand Down
124 changes: 124 additions & 0 deletions packages/admin-ui/src/DataList/DataList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useMemo } from "react";
import isEmpty from "lodash/isEmpty";
import { Heading } from "~/Heading";
import {
DataListModal,
Filters,
Loader,
MultiSelectActions,
MultiSelectAll,
NoData,
Pagination,
RefreshButton,
Sorters
} from "~/DataList/components";
import { DataListProps } from "~/DataList/types";

const defaultDataListProps = {
children: null,
title: null,
data: null,
meta: null,
loading: false,
refresh: () => {
return void 0;
},
setPage: null,
setPerPage: null,
perPageOptions: [10, 25, 50],
filters: null,
sorters: null,
setSorters: null,
actions: null,
multiSelectAll: () => {},
isAllMultiSelected: () => false,
isNoneMultiSelected: () => false,
loader: <Loader />,
noData: <NoData />,
showOptions: {
refresh: true,
pagination: true,
sorters: true,
filters: true
}
};

export const DataList = <TData,>(propsInput: DataListProps<TData>) => {
let render: React.ReactNode | null;

const props = useMemo(() => {
return {
...defaultDataListProps,
...propsInput
};
}, [propsInput]);

if (props.loading) {
render = props.loader;
} else if (isEmpty(props.data)) {
render = props.noData;
} else {
const ch = props.children;
render = typeof ch === "function" ? ch(props) : null;
}

const showOptions = props.showOptions || {};

return (
<div data-testid={"ui.list.data-list"}>
<div className={"wby-pt-md-extra wby-pb-md wby-px-md wby-border"}>
{(props.title || props.actions) && (
<div className={"wby-flex wby-justify-between wby-items-center wby-mb-md-plus"}>
<Heading
className={"wby-text-accent-primary"}
level={4}
text={props.title}
/>
<div className={"wby-flex wby-items-center wby-justify-end wby-gap-xs"}>
{props.actions}
</div>
</div>
)}

{Object.keys(showOptions).length > 0 && (
<div
className={
"wby-flex wby-items-center wby-justify-space-between wby-gap-sm-extra"
}
>
<div className={"wby-flex-1"}>
{props.search ? React.cloneElement(props.search, props) : null}
</div>
<div
className={
"wby-flex wby-items-center wby-justify-space-between wby-gap-xs"
}
>
<MultiSelectAll {...props} />
{showOptions.refresh && <RefreshButton {...props} />}
{showOptions.pagination && <Pagination {...props} />}
{showOptions.sorters && <Sorters {...props} />}
{showOptions.filters && <Filters {...props} />}
{props.modalOverlayAction && props.modalOverlay && (
<DataListModal
trigger={props.modalOverlayAction}
content={props.modalOverlay}
/>
)}
<MultiSelectActions {...props} />
</div>
</div>
)}
</div>

<div
className={
"wby-relative wby-h-full wby-overflow-auto wby-border-t-sm wby-border-t-neutral-dimmed webiny-data-list__content"
}
>
{props.subHeader}
{render}
</div>
</div>
);
};
170 changes: 170 additions & 0 deletions packages/admin-ui/src/DataList/DataListIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from "react";
import { ReactComponent as AutoRenew } from "@material-design-icons/svg/outlined/autorenew.svg";
import { ReactComponent as Delete } from "@material-design-icons/svg/outlined/delete.svg";
import { ReactComponent as Edit } from "@material-design-icons/svg/outlined/edit.svg";
import { ReactComponent as Sort } from "@material-design-icons/svg/outlined/sort.svg";
import { ReactComponent as Filter } from "@material-design-icons/svg/outlined/filter_alt.svg";
import { ReactComponent as NavigateBefore } from "@material-design-icons/svg/outlined/chevron_left.svg";
import { ReactComponent as NavigateAfter } from "@material-design-icons/svg/outlined/chevron_right.svg";
import { ReactComponent as Tune } from "@material-design-icons/svg/outlined/tune.svg";
import { ReactComponent as Download } from "@material-design-icons/svg/outlined/file_download.svg";
import { ReactComponent as Upload } from "@material-design-icons/svg/outlined/file_upload.svg";
import { ReactComponent as ListView } from "@material-design-icons/svg/outlined/list.svg";
import { ReactComponent as Clone } from "@material-design-icons/svg/outlined/library_add.svg";
import { ReactComponent as Login } from "@material-design-icons/svg/outlined/login.svg";
import { IconButton, IconButtonProps } from "~/Button";
import { List } from "~/List";

export const RefreshIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<AutoRenew />} label={"Refresh"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const DeleteIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Delete />} label={"Delete"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const CreateIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Edit />} label={"Create"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const EditIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Edit />} label={"Edit"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const SortIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Sort />} label={"Sort"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const FilterIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Filter />} label={"Filter"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const PreviousPageIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<NavigateBefore />} label={"Previous Page"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const NextPageIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<NavigateAfter />} label={"Next Page"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const OptionsIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Tune />} label={"Options"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const DownloadIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Download />} label={"Download"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const UploadIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Upload />} label={"Upload"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const ListIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<ListView />} label={"List"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const CloneIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Clone />} label={"Clone"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};

export const LoginIcon = (props: IconButtonProps) => {
return (
<IconButton
icon={<List.Item.Icon icon={<Login />} label={"Login"} />}
variant={"ghost"}
size={"sm"}
{...props}
/>
);
};
Loading