diff --git a/.changeset/poor-queens-doubt.md b/.changeset/poor-queens-doubt.md new file mode 100644 index 00000000000..a72c0187ab7 --- /dev/null +++ b/.changeset/poor-queens-doubt.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +The gift card modals now use new macaw-ui components. diff --git a/playwright/pages/dialogs/issueGiftCardDialog.ts b/playwright/pages/dialogs/issueGiftCardDialog.ts index 35ef094a112..5993840aa02 100644 --- a/playwright/pages/dialogs/issueGiftCardDialog.ts +++ b/playwright/pages/dialogs/issueGiftCardDialog.ts @@ -12,13 +12,13 @@ export class IssueGiftCardDialog extends BasePage { readonly giftCardExpireFields = page.getByTestId("gift-card-expire-data-fields"), readonly sendToCustomerCheckbox = page .getByTestId("send-to-customer-section") - .locator('input[type="checkbox"]'), - readonly sendExpireDateCheckbox = page.getByTestId("expiry-section").locator("input"), + .locator('button[role="checkbox"]'), + readonly sendExpireDateCheckbox = page.getByTestId("expiry-section").locator("button"), readonly customerInput = page.getByTestId("customer-field"), - readonly noteTextArea = page.getByTestId("note-field").locator('[name="note"]'), + readonly noteTextArea = page.getByTestId("note-field"), readonly requiresActivationCheckbox = page .getByTestId("requires-activation-section") - .locator("input"), + .locator('button[role="checkbox"]'), readonly issueButton = page.getByTestId("submit"), readonly okButton = page.getByTestId("submit"), readonly copyCodeButton = page.getByTestId("copy-code-button"), @@ -94,4 +94,8 @@ export class IssueGiftCardDialog extends BasePage { return allTexts[0]; } + + async blur() { + await this.page.click("[data-test-id='gift-card-dialog']"); + } } diff --git a/playwright/pages/dialogs/resendGiftCardCodeDialog.ts b/playwright/pages/dialogs/resendGiftCardCodeDialog.ts index dff92b0318f..23937f4cb46 100644 --- a/playwright/pages/dialogs/resendGiftCardCodeDialog.ts +++ b/playwright/pages/dialogs/resendGiftCardCodeDialog.ts @@ -10,6 +10,10 @@ export class ResendGiftCardCodeDialog { this.page = page; } + async blur() { + await this.page.keyboard.press("Tab"); + } + async clickResendButton() { await this.resendButton.click(); await this.resendButton.waitFor({ state: "hidden" }); diff --git a/playwright/pages/giftCardsPage.ts b/playwright/pages/giftCardsPage.ts index 604e6a4ad19..86bcef4429c 100644 --- a/playwright/pages/giftCardsPage.ts +++ b/playwright/pages/giftCardsPage.ts @@ -30,7 +30,9 @@ export class GiftCardsPage extends BasePage { readonly resendCodeButton = page.getByTestId("resend-code"), readonly deactivateButton = page.getByTestId("enable-button"), readonly saveButton = page.getByTestId("button-bar-confirm"), - readonly cardExpiresCheckboxOnModal = page.getByTestId("expiry-section").locator("input"), + readonly cardExpiresCheckboxOnModal = page + .getByTestId("expiry-section") + .locator('button[role="checkbox"]'), readonly giftCardExpiresCheckbox = page .getByTestId("gift-card-expire-section") .locator("input"), diff --git a/playwright/tests/giftCards.spec.ts b/playwright/tests/giftCards.spec.ts index 5e6c4e81495..f705482c457 100644 --- a/playwright/tests/giftCards.spec.ts +++ b/playwright/tests/giftCards.spec.ts @@ -19,6 +19,7 @@ test("TC: SALEOR_105 Issue gift card @e2e @gift", async () => { await giftCardsPage.clickIssueCardButton(); await giftCardsPage.issueGiftCardDialog.typeAmount("50"); await giftCardsPage.issueGiftCardDialog.typeCustomTag("super ultra automation discount"); + await giftCardsPage.issueGiftCardDialog.blur(); await giftCardsPage.issueGiftCardDialog.clickRequiresActivationCheckbox(); await giftCardsPage.issueGiftCardDialog.clickIssueButton(); await expect(giftCardsPage.issueGiftCardDialog.cardCode).toBeVisible(); @@ -68,6 +69,9 @@ test("TC: SALEOR_106 Issue gift card with specific customer and expiry date @e2e test("TC: SALEOR_107 Resend code @e2e @gift", async () => { await giftCardsPage.clickListRowBasedOnContainingText(GIFT_CARDS.giftCardToResendCode.name); await giftCardsPage.clickResendCodeButton(); + // This is a workaround for the issue with the dropdown focusing on dialog open + // Dropdown can cover the resend button and cause test to fail + await giftCardsPage.resendGiftCardCodeDialog.blur(); await giftCardsPage.resendGiftCardCodeDialog.clickResendButton(); await giftCardsPage.expectSuccessBanner(); }); diff --git a/src/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent.tsx b/src/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent.tsx index c2461f78fd5..1931f97417f 100644 --- a/src/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent.tsx +++ b/src/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent.tsx @@ -1,11 +1,11 @@ // @ts-strict-ignore import { ChannelData } from "@dashboard/channels/utils"; import { DateTimeTimezoneField } from "@dashboard/components/DateTimeTimezoneField"; -import { RadioGroup } from "@dashboard/components/RadioGroup"; +import { StopPropagation } from "@dashboard/components/StopPropagation"; import useCurrentDate from "@dashboard/hooks/useCurrentDate"; import useDateLocalize from "@dashboard/hooks/useDateLocalize"; import { getFormErrors, getProductErrorMessage } from "@dashboard/utils/errors"; -import { Box, Checkbox, Divider, Text } from "@saleor/macaw-ui-next"; +import { Box, Checkbox, Divider, RadioGroup, Text } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { useIntl } from "react-intl"; @@ -56,41 +56,48 @@ export const ChannelAvailabilityItemContent: React.FC = ({ return ( - { - onChange(id, { - ...formData, - isPublished: value === "true", - publishedAt: value === "false" ? null : publishedAt, - }); - }} - disabled={disabled} - display="flex" - flexDirection="column" - gap={3} - > - - - {messages.visibleLabel} - {isPublished && publishedAt && Date.parse(publishedAt) < dateNow && ( - - {messages.visibleSecondLabel || visibleMessage(publishedAt)} - - )} - - - - - {messages.hiddenLabel} - {publishedAt && !isPublished && Date.parse(publishedAt) >= dateNow && ( - - {messages.hiddenSecondLabel} - - )} - - - + {/** + * StopPropagation is used here to block onClick events from RadioGroup that cause throw error in datagrid + * Datagrid listing for all on click event but RadioGroup emitted couple of events at once + * Radix issue: https://github.com/radix-ui/primitives/issues/1982 + */} + + { + onChange(id, { + ...formData, + isPublished: value === "true", + publishedAt: value === "false" ? null : publishedAt, + }); + }} + disabled={disabled} + display="flex" + flexDirection="column" + gap={3} + > + + + {messages.visibleLabel} + {isPublished && publishedAt && Date.parse(publishedAt) < dateNow && ( + + {messages.visibleSecondLabel || visibleMessage(publishedAt)} + + )} + + + + + {messages.hiddenLabel} + {publishedAt && !isPublished && Date.parse(publishedAt) >= dateNow && ( + + {messages.hiddenSecondLabel} + + )} + + + + {!isPublished && ( { - // StopProgation is used here to block onClick events from RadioGroup that cause throw error in datagrid - // Datagrid listing for all on click event but RadioGroup emitated couple events at onced - // Radix issue: https://github.com/radix-ui/primitives/issues/1982 - return ( - - - - ); -}; - -export const RadioGroup = Object.assign(RadioGroupRoot, { - Item: RadioGroupBase.Item, -}); diff --git a/src/components/RadioGroup/index.ts b/src/components/RadioGroup/index.ts deleted file mode 100644 index da254de2986..00000000000 --- a/src/components/RadioGroup/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./RadioGroup"; diff --git a/src/components/RadioGroupField/RadioGroupField.tsx b/src/components/RadioGroupField/RadioGroupField.tsx index 3c90b1f2c1e..67dd22dc930 100644 --- a/src/components/RadioGroupField/RadioGroupField.tsx +++ b/src/components/RadioGroupField/RadioGroupField.tsx @@ -11,6 +11,7 @@ import clsx from "clsx"; import React from "react"; import { FormattedMessage } from "react-intl"; +import { SimpleRadioGroupField } from "../SimpleRadioGroupField"; import { useStyles } from "./styles"; export interface RadioGroupFieldChoice { @@ -34,6 +35,8 @@ interface RadioGroupFieldProps { onChange: (event: React.ChangeEvent) => void; } +export const NewRadioGroupField = SimpleRadioGroupField; + export const RadioGroupField: React.FC = props => { const { alignTop, diff --git a/src/components/SimpleRadioGroupField.test.tsx b/src/components/SimpleRadioGroupField.test.tsx new file mode 100644 index 00000000000..51081ff4a00 --- /dev/null +++ b/src/components/SimpleRadioGroupField.test.tsx @@ -0,0 +1,98 @@ +import "@testing-library/jest-dom/extend-expect"; + +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; + +import { SimpleRadioGroupField } from "./SimpleRadioGroupField"; + +const choices = [ + { label: "Choice 1", value: "choice1" }, + { label: "Choice 2", value: "choice2", disabled: true }, + { label: "Choice 3", value: "choice3" }, +]; + +describe("SimpleRadioGroupField", () => { + it("renders radio fields correctly", () => { + // Arrange & Act + render( + , + ); + + // Assert + expect(screen.getByText("Choice 1")).toBeInTheDocument(); + expect(screen.getByText("Choice 2")).toBeInTheDocument(); + expect(screen.getByText("Choice 3")).toBeInTheDocument(); + }); + + it("calls onChange when a radio button is clicked", () => { + // Arrange + const handleChange = jest.fn(); + + render( + , + ); + + // Act + const radioButton = screen.getByLabelText("Choice 3"); + + fireEvent.click(radioButton); + + // Assert + expect(handleChange).toHaveBeenCalledWith({ + target: { value: "choice3", name: "testRadioGroup" }, + }); + }); + + it("doesn't call onChange when `disabled` item is clicked", () => { + // Arrange + const handleChange = jest.fn(); + + render( + , + ); + + // Act + const radioButton = screen.getByLabelText("Choice 2"); + + fireEvent.click(radioButton); + + // Assert + expect(handleChange).not.toHaveBeenCalledWith({ + target: { value: "choice2", name: "testRadioGroup" }, + }); + }); + + it("displays the error message when provided", () => { + // Arrange & Act + const errorMessage = "Error message"; + + render( + , + ); + + // Assert + expect(screen.getByText(errorMessage)).toBeInTheDocument(); + }); +}); diff --git a/src/components/SimpleRadioGroupField.tsx b/src/components/SimpleRadioGroupField.tsx new file mode 100644 index 00000000000..70c9eaa7a2c --- /dev/null +++ b/src/components/SimpleRadioGroupField.tsx @@ -0,0 +1,64 @@ +import { ChangeEvent } from "@dashboard/hooks/useForm"; +import { RadioGroup, RadioGroupRootProps, Text } from "@saleor/macaw-ui-next"; +import React from "react"; + +type RadioGroupFieldChoice = { + label: string | React.ReactNode; + value: string; + disabled?: boolean; +}; + +interface SimpleRadioGroupFieldProps + extends Omit { + name: string; + onChange: (event: ChangeEvent) => void; + choices: RadioGroupFieldChoice[]; + size?: RadioGroupRootProps["size"]; + errorMessage?: string; +} + +// SimpleRadioGroupField is a migration of RadioGroupField "@dashboard/components/RadioGroupField" using Macaw UI +// While migrating to this component note that it doesn't have a label, hint or 'no choices' message. +export const SimpleRadioGroupField: React.FC = ({ + name, + value, + error, + onChange, + choices, + size = "large", + errorMessage, + ...props +}) => { + return ( + <> + onChange({ target: { value, name } })} + {...props} + > + {choices.map(({ label, value, disabled }) => ( + + + {label} + + + ))} + + + {errorMessage && {errorMessage}} + + ); +}; diff --git a/src/components/TextWithSelectField/TextWithSelectField.tsx b/src/components/TextWithSelectField/TextWithSelectField.tsx index a945cb957b7..8675cc71742 100644 --- a/src/components/TextWithSelectField/TextWithSelectField.tsx +++ b/src/components/TextWithSelectField/TextWithSelectField.tsx @@ -1,15 +1,10 @@ import { ChangeEvent, FormChange } from "@dashboard/hooks/useForm"; -import { TextField } from "@material-ui/core"; -import { Box, Option, Select, Spinner } from "@saleor/macaw-ui-next"; -import clsx from "clsx"; +import { Box, Input, Option, Select, Spinner } from "@saleor/macaw-ui-next"; import React from "react"; -import { useStyles } from "./styles"; - interface CommonFieldProps { name: string; type?: string; - className?: string; label?: string; } @@ -24,20 +19,17 @@ export interface TextWithSelectFieldProps { minValue?: number; }; selectFieldProps: CommonFieldProps & { value: string }; - containerClassName?: string; } const TextWithSelectField: React.FC = ({ change, choices, loading, - containerClassName, textFieldProps, selectFieldProps, helperText, isError, }) => { - const classes = useStyles(); const { name: textFieldName, value: textFieldValue, @@ -45,11 +37,7 @@ const TextWithSelectField: React.FC = ({ type: textFieldType, minValue: textFieldMinValue, } = textFieldProps; - const { - name: selectFieldName, - value: selectFieldValue, - className: selectFieldClassName, - } = selectFieldProps; + const { name: selectFieldName, value: selectFieldValue } = selectFieldProps; const handleTextChange = (event: ChangeEvent) => { const { value } = event.target; const otherTarget = { @@ -68,22 +56,18 @@ const TextWithSelectField: React.FC = ({ }; return ( -
- + @@ -92,15 +76,14 @@ const TextWithSelectField: React.FC = ({ name={selectFieldName} onChange={value => change({ target: { name: selectFieldName, value } })} value={selectFieldValue} - className={clsx("noBorder", selectFieldClassName)} + className="noBorder" + __width="50px" options={choices} /> - ), - }} - onChange={handleTextChange} - value={textFieldValue} + ) + } /> -
+
); }; diff --git a/src/components/TextWithSelectField/styles.ts b/src/components/TextWithSelectField/styles.ts deleted file mode 100644 index 5109af709f3..00000000000 --- a/src/components/TextWithSelectField/styles.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { makeStyles } from "@saleor/macaw-ui"; - -export const useStyles = makeStyles( - () => ({ - container: { - width: 400, - }, - innerContainer: { - width: "100%", - }, - textField: { - width: "100%", - paddingRight: 0, - "& input": { - maxWidth: "100%", - }, - }, - textFieldCentered: { - "& input": { - paddingTop: 17, - paddingBottom: 16, - }, - }, - - // It may seem lazy to set these CSS properties with !important tags, but - // specificity of overriden styles is really high and refer to mutliple - // internal classes. Instead of dealing with it and trying to create - // complex selectors with volatile class names, it's both easier and safer - // to enforce these styles with !important. - input: { - boxShadow: "none !important", - border: "none", - }, - noBorder: { - borderColor: "transparent !important", - }, - noBackground: { - backgroundColor: "transparent !important", - }, - }), - { name: "TextWithSelectField" }, -); diff --git a/src/giftCards/GiftCardBulkCreateDialog/GiftCardBulkCreateDialogForm.tsx b/src/giftCards/GiftCardBulkCreateDialog/GiftCardBulkCreateDialogForm.tsx index 39434c6eac3..0cf1e8f57f1 100644 --- a/src/giftCards/GiftCardBulkCreateDialog/GiftCardBulkCreateDialogForm.tsx +++ b/src/giftCards/GiftCardBulkCreateDialog/GiftCardBulkCreateDialogForm.tsx @@ -18,7 +18,6 @@ import GiftCardCreateExpirySelect from "../GiftCardCreateDialog/GiftCardCreateEx import GiftCardCreateMoneyInput from "../GiftCardCreateDialog/GiftCardCreateMoneyInput"; import GiftCardCreateRequiresActivationSection from "../GiftCardCreateDialog/GiftCardCreateRequiresActivationSection"; import { giftCardCreateMessages as messages } from "../GiftCardCreateDialog/messages"; -import { useGiftCardCreateFormStyles as useStyles } from "../GiftCardCreateDialog/styles"; import { getGiftCardErrorMessage } from "../GiftCardUpdate/messages"; import { GiftCardBulkCreateFormCommonProps, @@ -53,7 +52,6 @@ const GiftCardBulkCreateDialogForm: React.FC formErrors = {}, }) => { const intl = useIntl(); - const classes = useStyles({}); const { data: settingsData, loading: loadingSettings } = useGiftCardSettingsQuery(); const getInitialExpirySettingsData = (): Partial => { if (loadingSettings) { @@ -94,7 +92,7 @@ const GiftCardBulkCreateDialogForm: React.FC error={!!formErrors?.count} name="cardsAmount" onChange={change} - className={classes.fullWidthContainer} + fullWidth label={intl.formatMessage(messages.giftCardsAmountLabel)} value={cardsAmount} helperText={getGiftCardErrorMessage(formErrors?.count, intl)} diff --git a/src/giftCards/GiftCardCreateDialog/GiftCardBulkCreateSuccessDialog.tsx b/src/giftCards/GiftCardCreateDialog/GiftCardBulkCreateSuccessDialog.tsx index a8f0f7185e0..1052e065c0b 100644 --- a/src/giftCards/GiftCardCreateDialog/GiftCardBulkCreateSuccessDialog.tsx +++ b/src/giftCards/GiftCardCreateDialog/GiftCardBulkCreateSuccessDialog.tsx @@ -1,7 +1,6 @@ -import { Button } from "@dashboard/components/Button"; import { DashboardModal } from "@dashboard/components/Modal"; import { DialogProps } from "@dashboard/types"; -import { Text } from "@saleor/macaw-ui-next"; +import { Button, Text } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; diff --git a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogCodeContent.tsx b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogCodeContent.tsx index a5c93295b1f..3e04361154a 100644 --- a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogCodeContent.tsx +++ b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogCodeContent.tsx @@ -1,9 +1,8 @@ -import { Button } from "@dashboard/components/Button"; import { DashboardModal } from "@dashboard/components/Modal"; import useClipboard from "@dashboard/hooks/useClipboard"; import useNotifier from "@dashboard/hooks/useNotifier"; import { buttonMessages } from "@dashboard/intl"; -import { Text } from "@saleor/macaw-ui-next"; +import { Button, Text } from "@saleor/macaw-ui-next"; import React from "react"; import { useIntl } from "react-intl"; @@ -32,12 +31,18 @@ const GiftCardCreateDialogCodeContent: React.FC {intl.formatMessage(messages.createdGiftCardLabel)} - + {cardCode} -