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:
parent
e90a2fd757
commit
1d4c4d878e
9 changed files with 294 additions and 31 deletions
|
@ -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']",
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface CardMenuItem {
|
|||
loading?: boolean;
|
||||
withLoading?: boolean;
|
||||
hasError?: boolean;
|
||||
Icon?: React.ReactElement;
|
||||
}
|
||||
|
||||
export interface CardMenuProps {
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
206
src/components/Datagrid/RowActions.test.tsx
Normal file
206
src/components/Datagrid/RowActions.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
51
src/components/Datagrid/RowActions.tsx
Normal file
51
src/components/Datagrid/RowActions.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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
22
src/icons/Edit.tsx
Normal 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;
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue