Datagrid - show icon button when single menu item with icon (#2862)

* RowMenu component

* Show first menu item as icon

* RowAction tests

* Change Edit button

* Remove outline from colum picker

* Change ghost color

* Update enterVariantEditPage test

* Update test id in test for edit button

* Update editVariant selector in test
This commit is contained in:
poulch 2022-12-16 20:59:39 +01:00 committed by GitHub
parent e90a2fd757
commit 1d4c4d878e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 294 additions and 31 deletions

View file

@ -25,7 +25,7 @@ export const PRODUCT_DETAILS = {
uploadSavedImagesButton: '[data-test-id="upload-images"]',
uploadMediaUrlButton: '[data-test-id="upload-media-url"]',
saveUploadUrlButton: '[data-test-id="upload-url-button"]',
editVariant: '[id="menu-list-grow"]',
editVariant: '[data-test-id="row-action-button"]',
firstRowDataGrid: "[data-testid='glide-cell-1-0']",
dataGridTable: "[data-testid='data-grid-canvas']",
};

View file

@ -154,8 +154,6 @@ export function enterVariantEditPage() {
cy.get(PRODUCT_DETAILS.dataGridTable)
.should("be.visible")
.wait(1000)
.get(BUTTON_SELECTORS.showMoreButton)
.click()
.get(PRODUCT_DETAILS.editVariant)
.click();
}

View file

@ -25,6 +25,7 @@ export interface CardMenuItem {
loading?: boolean;
withLoading?: boolean;
hasError?: boolean;
Icon?: React.ReactElement;
}
export interface CardMenuProps {

View file

@ -9,17 +9,18 @@ import DataEditor, {
} from "@glideapps/glide-data-grid";
import { Card, CardContent, Typography } from "@material-ui/core";
import { usePreventHistoryBack } from "@saleor/hooks/usePreventHistoryBack";
import { MoreHorizontalIcon, useTheme } from "@saleor/macaw-ui";
import { useTheme } from "@saleor/macaw-ui";
import clsx from "clsx";
import range from "lodash/range";
import throttle from "lodash/throttle";
import React from "react";
import { FormattedMessage } from "react-intl";
import CardMenu, { CardMenuItem } from "../CardMenu";
import { CardMenuItem } from "../CardMenu";
import ColumnPicker from "../ColumnPicker";
import { FullScreenContainer } from "./FullScreenContainer";
import { Header } from "./Header";
import { RowActions } from "./RowActions";
import useStyles, { useDatagridTheme, useFullScreenStyles } from "./styles";
import { AvailableColumn } from "./types";
import useCells from "./useCells";
@ -280,8 +281,8 @@ export const Datagrid: React.FC<DatagridProps> = ({
<div className={classes.columnPicker}>
<ColumnPicker
IconButtonProps={{
className: classes.columnPickerBtn,
variant: "secondary",
className: classes.ghostIcon,
variant: "ghost",
hoverOutline: false,
}}
availableColumns={availableColumnsChoices}
@ -305,26 +306,10 @@ export const Datagrid: React.FC<DatagridProps> = ({
{Array(rowsTotal)
.fill(0)
.map((_, index) => (
<div
className={clsx(classes.rowAction, {
[classes.rowActionSelected]: selection?.rows.hasIndex(
index,
),
[classes.rowActionScrolledToRight]: scrolledToRight,
})}
key={index}
>
<CardMenu
disabled={index >= rowsTotal - added.length}
Icon={MoreHorizontalIcon}
IconButtonProps={{
className: classes.columnPickerBtn,
hoverOutline: false,
state: "default",
}}
<RowActions
menuItems={menuItems(index)}
disabled={index >= rowsTotal - added.length}
/>
</div>
))}
</div>
}

View file

@ -0,0 +1,206 @@
import { EditIcon, ThemeProvider } from "@saleor/macaw-ui";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { RowActions } from "./RowActions";
describe("RowActions", () => {
it("should render empty when menu items count equal to 0", () => {
// Arrange & Act
const { container } = render(
<ThemeProvider>
<RowActions menuItems={[]} disabled={false} />
</ThemeProvider>,
);
// Assert
expect(container.firstChild).toBeNull();
});
it("should render icon button when only one menu item and has icon props", () => {
// Arrange & Act
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: jest.fn(),
Icon: <EditIcon data-test-id="edit-icon" />,
},
]}
disabled={false}
/>
</ThemeProvider>,
);
// Assert
expect(screen.getByTestId("row-action-button")).toBeInTheDocument();
expect(screen.getByTestId("edit-icon")).toBeInTheDocument();
});
it("should render card meu when only one menu item and has no icon props", () => {
// Arrange & Act
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: jest.fn(),
},
]}
disabled={false}
/>
</ThemeProvider>,
);
// Assert
expect(screen.getByTestId("show-more-button")).toBeInTheDocument();
});
it("should render card meu with multiple items", async () => {
// Arrange
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: jest.fn(),
testId: "edit-button",
},
{
label: "Delete",
onSelect: jest.fn(),
testId: "delete-button",
},
{
label: "Upgrade",
onSelect: jest.fn(),
testId: "upgrade-button",
},
]}
disabled={false}
/>
</ThemeProvider>,
);
// Act
await userEvent.click(screen.getByTestId("show-more-button"));
// Assert
expect(screen.getByTestId("show-more-button")).toBeInTheDocument();
expect(screen.getByTestId("edit-button")).toBeInTheDocument();
expect(screen.getByTestId("delete-button")).toBeInTheDocument();
expect(screen.getByTestId("upgrade-button")).toBeInTheDocument();
});
it("should fire callback when click on icon button when single menu item with icon props", async () => {
// Arrange
const onSelectCallback = jest.fn();
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: onSelectCallback,
Icon: <EditIcon />,
},
]}
disabled={false}
/>
</ThemeProvider>,
);
// Act
await userEvent.click(screen.getByTestId("row-action-button"));
// Assert
expect(onSelectCallback).toHaveBeenCalled();
});
it("should fire callback when click on icon button when multiple menu item", async () => {
// Arrange
const onIconClickCallback = jest.fn();
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: onIconClickCallback,
testId: "edit-button",
},
{
label: "Delete",
onSelect: jest.fn(),
},
{
label: "Upgrade",
onSelect: jest.fn(),
},
]}
disabled={false}
/>
</ThemeProvider>,
);
// Act
await userEvent.click(screen.getByTestId("show-more-button"));
await userEvent.click(screen.getByTestId("edit-button"));
// Assert
expect(onIconClickCallback).toHaveBeenCalled();
});
it("should disabled show more button when RowAction disabled", async () => {
// Arrange & Act
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: jest.fn(),
},
{
label: "Delete",
onSelect: jest.fn(),
},
]}
disabled={true}
/>
</ThemeProvider>,
);
// Assert
expect(screen.getByTestId("show-more-button")).toBeDisabled();
});
it("should disabled row action button when RowAction disabled", async () => {
// Arrange & Act
render(
<ThemeProvider>
<RowActions
menuItems={[
{
label: "Edit",
onSelect: jest.fn(),
Icon: <EditIcon />,
},
]}
disabled={true}
/>
</ThemeProvider>,
);
// Assert
expect(screen.getByTestId("row-action-button")).toBeDisabled();
});
});

View file

@ -0,0 +1,51 @@
import { IconButton, MoreHorizontalIcon } from "@saleor/macaw-ui";
import React from "react";
import CardMenu, { CardMenuItem } from "../CardMenu";
import useStyles from "./styles";
interface RowActionsProps {
menuItems: CardMenuItem[];
disabled?: boolean;
}
export const RowActions = ({ menuItems, disabled }: RowActionsProps) => {
const classes = useStyles();
const hasSingleMenuItem = menuItems.length === 1;
const firstMenuItem = menuItems[0];
const handleIconClick = () => {
firstMenuItem.onSelect();
};
if (!menuItems.length) {
return null;
}
return (
<div className={classes.rowAction}>
{hasSingleMenuItem && firstMenuItem.Icon ? (
<IconButton
data-test-id="row-action-button"
disabled={disabled}
onClick={handleIconClick}
className={classes.ghostIcon}
variant="ghost"
>
{firstMenuItem.Icon}
</IconButton>
) : (
<CardMenu
disabled={disabled}
Icon={MoreHorizontalIcon}
IconButtonProps={{
className: classes.ghostIcon,
hoverOutline: false,
state: "default",
}}
menuItems={menuItems}
/>
)}
</div>
);
};

View file

@ -34,10 +34,8 @@ const useStyles = makeStyles(
justifyContent: "center",
height: 48,
},
columnPickerBtn: {
"&:hover": {
color: theme.palette.saleor.main[1],
},
ghostIcon: {
color: theme.palette.saleor.main[3],
},
portal: {
"& input::-webkit-outer-spin-button, input::-webkit-inner-spin-button": {

22
src/icons/Edit.tsx Normal file
View file

@ -0,0 +1,22 @@
import React from "react";
const EditIcon = () => (
<svg
width="15"
height="14"
viewBox="0 0 15 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.1791 0.678585C12.5282 0.0277112 11.4729 0.0277113 10.822 0.678585L10.0897 1.41088L13.036 4.35716L13.7683 3.62486C14.4192 2.97399 14.4192 1.91871 13.7683 1.26784L13.1791 0.678585Z"
fill="currentColor"
/>
<path
d="M11.8575 5.53567L8.91123 2.58939L1.04876 10.4519C0.892484 10.6081 0.804688 10.8201 0.804688 11.0411V12.8089C0.804688 13.2691 1.17778 13.6422 1.63802 13.6422H3.40579C3.6268 13.6422 3.83876 13.5544 3.99504 13.3981L11.8575 5.53567Z"
fill="currentColor"
/>
</svg>
);
export default EditIcon;

View file

@ -12,6 +12,7 @@ import {
RefreshLimitsQuery,
WarehouseFragment,
} from "@saleor/graphql";
import EditIcon from "@saleor/icons/Edit";
import { buttonMessages } from "@saleor/intl";
import { Button } from "@saleor/macaw-ui";
import { ProductVariantListError } from "@saleor/products/views/ProductUpdate/handlers/errors";
@ -121,6 +122,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = ({
{
label: "Edit Variant",
onSelect: () => onRowClick(variants[index].id),
Icon: <EditIcon />,
},
]}
rows={variants?.length ?? 0}