diff --git a/.changeset/hot-coins-exercise.md b/.changeset/hot-coins-exercise.md new file mode 100644 index 00000000000..ceb793e26aa --- /dev/null +++ b/.changeset/hot-coins-exercise.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Migrate product page to new macaw components diff --git a/cypress/elements/catalog/products/product-details.js b/cypress/elements/catalog/products/product-details.js index a99a9db2924..7462491771e 100644 --- a/cypress/elements/catalog/products/product-details.js +++ b/cypress/elements/catalog/products/product-details.js @@ -14,7 +14,7 @@ export const PRODUCT_DETAILS = { skuInput: "[name='sku']", variantRow: "[data-test-id='product-variant-row']", variantPrice: '[data-test-id="price"]', - collectionRemoveButtons: '[data-test-id="collection-remove"]', + collectionRemoveButtons: '[data-test-id*="selected-option"] span:last-child', addWarehouseButton: '[data-test-id="add-warehouse"]', warehouseOption: "[role='menuitem']", stockInput: '[data-test-id="stock-input"]', diff --git a/cypress/elements/catalog/products/variants-selectors.js b/cypress/elements/catalog/products/variants-selectors.js index 44860f874e4..4f43a322c3a 100644 --- a/cypress/elements/catalog/products/variants-selectors.js +++ b/cypress/elements/catalog/products/variants-selectors.js @@ -1,8 +1,7 @@ export const VARIANTS_SELECTORS = { variantNameInput: '[data-test-id="variant-name"]', skuTextField: '[data-test-id="sku"]', - attributeOption: - "[data-test-type='option'], [data-test-id='multi-autocomplete-select-option']", + attributeOption: "[data-test-id='select-option']", attributeSelector: "[data-test-id='attribute-value']", addWarehouseButton: "[data-test-id='add-warehouse']", warehouseOption: "[role='menuitem']", diff --git a/cypress/elements/pages/page-details.js b/cypress/elements/pages/page-details.js index 5e23207c4e3..0f8079d1970 100644 --- a/cypress/elements/pages/page-details.js +++ b/cypress/elements/pages/page-details.js @@ -6,6 +6,6 @@ export const PAGE_DETAILS_SELECTORS = { isNotPublishedCheckbox: '[name="isPublished"][value=false]', uploadFileButton: '[data-test-id="button-upload-file"]', richTextEditorAttributeValue: '[class*="ce-paragraph"]', - booleanAttributeValueCheckbox: '[role="checkbox"]', + booleanAttributeValueCheckbox: '[data-macaw-ui-component="Toggle"]', numericAttributeValueInput: '[name*="attribute:"]', }; diff --git a/cypress/elements/shared/sharedElements.js b/cypress/elements/shared/sharedElements.js index 2111810999f..dbc674c14c0 100644 --- a/cypress/elements/shared/sharedElements.js +++ b/cypress/elements/shared/sharedElements.js @@ -53,6 +53,9 @@ export const SHARED_ELEMENTS = { }, warningDialog: '[data-test-id="warning-dialog"]', pageHeader: "[data-test-id='page-header']", + multiselect: { + selectedOptions: '[data-test-id*="selected-option-"]', + }, multiAutocomplete: { selectedOptions: '[id*="selected-option-"]', }, diff --git a/cypress/support/customCommands/sharedElementsOperations/selects.js b/cypress/support/customCommands/sharedElementsOperations/selects.js index fb507aa3327..8c556fe9d4f 100644 --- a/cypress/support/customCommands/sharedElementsOperations/selects.js +++ b/cypress/support/customCommands/sharedElementsOperations/selects.js @@ -41,15 +41,30 @@ Cypress.Commands.add("createNewOption", (selectSelector, newOption) => { .click({ force: true }); }); +Cypress.Commands.add("fillNewMultiSelect", (selectSelector, option) => { + cy.fillAutocompleteSelect(selectSelector, option, true).then( + returnedOption => { + cy.get(SHARED_ELEMENTS.header) + .first() + .click({ force: true }) + .get(SHARED_ELEMENTS.multiselect.selectedOptions) + .should("be.visible"); + return cy.wrap(returnedOption); + }, + ); +}); + Cypress.Commands.add("fillMultiSelect", (selectSelector, option) => { - cy.fillAutocompleteSelect(selectSelector, option).then(returnedOption => { - cy.get(SHARED_ELEMENTS.header) - .first() - .click({ force: true }) - .get(SHARED_ELEMENTS.multiAutocomplete.selectedOptions) - .should("be.visible"); - return cy.wrap(returnedOption); - }); + cy.fillAutocompleteSelect(selectSelector, option, true).then( + returnedOption => { + cy.get(SHARED_ELEMENTS.header) + .first() + .click({ force: true }) + .get(SHARED_ELEMENTS.multiAutocomplete.selectedOptions) + .should("be.visible"); + return cy.wrap(returnedOption); + }, + ); }); Cypress.Commands.add("fillBaseSelect", (selectSelector, value) => { @@ -60,46 +75,50 @@ Cypress.Commands.add("fillBaseSelect", (selectSelector, value) => { .click(); }); -Cypress.Commands.add("fillAutocompleteSelect", (selectSelector, option) => { - let selectedOption = option; - cy.get(selectSelector) - .click() - .get(BUTTON_SELECTORS.selectOption) - .should("be.visible"); - if (option) { - cy.get(BUTTON_SELECTORS.selectOption) - .first() - .then(detachedOption => { - cy.get(selectSelector).then(select => { - if (select.find("input").length > 0) { - cy.get(selectSelector) - .find("input") - .clear() - .type(option, { delay: 10 }); - } else { - cy.get(selectSelector).clear().type(option, { delay: 10 }); - } +Cypress.Commands.add( + "fillAutocompleteSelect", + (selectSelector, option, isForce = false) => { + let selectedOption = option; + console.log("isForce", isForce); + cy.get(selectSelector) + .click(isForce ? { force: true } : undefined) + .get(BUTTON_SELECTORS.selectOption) + .should("be.visible"); + if (option) { + cy.get(BUTTON_SELECTORS.selectOption) + .first() + .then(detachedOption => { + cy.get(selectSelector).then(select => { + if (select.find("input").length > 0) { + cy.get(selectSelector) + .find("input") + .clear() + .type(option, { delay: 10 }); + } else { + cy.get(selectSelector).clear().type(option, { delay: 10 }); + } + }); + cy.wrap(detachedOption).should(det => { + Cypress.dom.isDetached(det); + }); + cy.contains(BUTTON_SELECTORS.selectOption, option) + .should("be.visible") + .click({ force: true }) + .then(() => selectedOption); }); - cy.wrap(detachedOption).should(det => { - Cypress.dom.isDetached(det); + } else { + cy.get(BUTTON_SELECTORS.selectOption) + .wait(1000) + .first() + .invoke("text") + .then(text => { + selectedOption = text; }); - cy.contains(BUTTON_SELECTORS.selectOption, option) - .should("be.visible") - .click({ force: true }) - .then(() => selectedOption); - }); - } else { - cy.get(BUTTON_SELECTORS.selectOption) - .wait(1000) - .first() - .invoke("text") - .then(text => { - selectedOption = text; - }); - return cy - .get(BUTTON_SELECTORS.selectOption) - .first() - .click() - .then(() => selectedOption); - } -}); + return cy + .get(BUTTON_SELECTORS.selectOption) + .first() + .click() + .then(() => selectedOption); + } + }, +); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 065b53f1a55..e8ffed3e8da 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -39,10 +39,12 @@ declare namespace Cypress { fillAutocompleteSelect( selectSelector: string, option?: string, + isForce?: boolean, ): Chainable; addAliasToGraphRequest(alias: string): Chainable; sendRequestWithQuery(query: string): Chainable; fillMultiSelect(selectSelector: string, option: string): Chainable; + fillNewMultiSelect(selectSelector: string, option: string): Chainable; createNewOption(selectSelector: string, newOption: string): Chainable; findElementsAndMakeActionOnTable({ elementsGraphqlAlias: string, diff --git a/cypress/support/pages/catalog/products/VariantsPage.js b/cypress/support/pages/catalog/products/VariantsPage.js index 2173b00b193..4b1ee2f575c 100644 --- a/cypress/support/pages/catalog/products/VariantsPage.js +++ b/cypress/support/pages/catalog/products/VariantsPage.js @@ -74,11 +74,11 @@ export function fillUpVariantDetails({ selectAttributeWithType({ attributeType, attributeName }); cy.get(PRICE_LIST.priceInput) .each(input => { - cy.wrap(input).type(price); + cy.wrap(input).type(price, { force: true }); }) .get(PRICE_LIST.costPriceInput) .each(input => { - cy.wrap(input).type(costPrice); + cy.wrap(input).type(costPrice, { force: true }); }); if (variantName) { cy.get(VARIANTS_SELECTORS.variantNameInput).type(variantName); diff --git a/cypress/support/pages/catalog/products/priceListComponent.js b/cypress/support/pages/catalog/products/priceListComponent.js index 8f033a0383f..55fc8644141 100644 --- a/cypress/support/pages/catalog/products/priceListComponent.js +++ b/cypress/support/pages/catalog/products/priceListComponent.js @@ -2,14 +2,14 @@ import { PRICE_LIST } from "../../../../elements/catalog/products/price-list"; export const priceInputLists = { sellingPrice: PRICE_LIST.priceInput, - costPrice: PRICE_LIST.costPriceInput + costPrice: PRICE_LIST.costPriceInput, }; export function fillUpPriceList( price = 1, - priceTypeInput = priceInputLists.sellingPrice + priceTypeInput = priceInputLists.sellingPrice, ) { cy.get(priceTypeInput).each($priceInput => { - cy.wrap($priceInput).type(price); + cy.wrap($priceInput).type(price, { force: true }); }); } diff --git a/cypress/support/pages/catalog/products/productDetailsPage.js b/cypress/support/pages/catalog/products/productDetailsPage.js index bcb57725e03..eca39fe6d9b 100644 --- a/cypress/support/pages/catalog/products/productDetailsPage.js +++ b/cypress/support/pages/catalog/products/productDetailsPage.js @@ -109,7 +109,7 @@ export function fillUpProductOrganization({ }) { const organization = {}; return cy - .fillAutocompleteSelect(PRODUCT_DETAILS.productTypeInput, productType) + .fillAutocompleteSelect(PRODUCT_DETAILS.productTypeInput, productType, true) .then(selected => { organization.productType = selected; fillUpCollectionAndCategory({ category, collection }); @@ -124,10 +124,10 @@ export function fillUpProductOrganization({ export function fillUpCollectionAndCategory({ category, collection }) { const organization = {}; return cy - .fillAutocompleteSelect(PRODUCT_DETAILS.categoryInput, category) + .fillAutocompleteSelect(PRODUCT_DETAILS.categoryInput, category, true) .then(selected => { organization.category = selected; - cy.fillMultiSelect(PRODUCT_DETAILS.collectionInput, collection); + cy.fillNewMultiSelect(PRODUCT_DETAILS.collectionInput, collection); }) .then(selected => { organization.collection = selected; diff --git a/cypress/support/pages/discounts/vouchersPage.js b/cypress/support/pages/discounts/vouchersPage.js index 36be9c3d515..8728da31ff0 100644 --- a/cypress/support/pages/discounts/vouchersPage.js +++ b/cypress/support/pages/discounts/vouchersPage.js @@ -49,7 +49,7 @@ export function createVoucher({ cy.get(VOUCHERS_SELECTORS.requirements.minOrderValueCheckbox) .click() .get(VOUCHERS_SELECTORS.requirements.minOrderValueInput) - .type(minOrderValue); + .type(minOrderValue, { force: true }); } if (minAmountOfItems) { cy.get(VOUCHERS_SELECTORS.requirements.minAmountOfItemsCheckbox) diff --git a/cypress/support/pages/ordersTransactionUtils.js b/cypress/support/pages/ordersTransactionUtils.js index 2d31b2b4d6e..91af8b77a7b 100644 --- a/cypress/support/pages/ordersTransactionUtils.js +++ b/cypress/support/pages/ordersTransactionUtils.js @@ -78,7 +78,9 @@ export function typeRefundReason(refundReasonDescription) { .type(`reason_${refundReasonDescription}`); } export function typeRefundTotalAmount(amount) { - return cy.get(ORDER_TRANSACTION_CREATE.transactAmountInput).type(amount); + return cy + .get(ORDER_TRANSACTION_CREATE.transactAmountInput) + .type(amount, { force: true }); } export function typeTransactionReference(reference) { return cy.get(ORDERS_SELECTORS.transactionReferenceInput).type(reference); diff --git a/cypress/support/pages/shippingMethodPage.js b/cypress/support/pages/shippingMethodPage.js index c618cf161b2..c26187e51ec 100644 --- a/cypress/support/pages/shippingMethodPage.js +++ b/cypress/support/pages/shippingMethodPage.js @@ -136,11 +136,11 @@ export function fillUpShippingRate({ } cy.get(SHIPPING_RATE_DETAILS.priceInput).each($priceInput => { cy.wrap($priceInput) - .clear() + .clear({ force: true }) .get(SHARED_ELEMENTS.pageHeader) .click() .wrap($priceInput) - .clearAndType(price); + .clearAndType(price, { force: true }); }); } @@ -189,16 +189,16 @@ export function saveRateAfterUpdate() { export function fillUpLimits({ max, min }) { cy.get(SHIPPING_RATE_DETAILS.minValueInput) - .type(min) + .type(min, { force: true }) .get(SHIPPING_RATE_DETAILS.maxValueInput) - .type(max); + .type(max, { force: true }); } export function fillUpDeliveryTime({ min, max }) { cy.get(SHIPPING_RATE_DETAILS.minDeliveryTimeInput) - .clearAndType(min) + .clearAndType(min, { force: true }) .get(SHIPPING_RATE_DETAILS.maxDeliveryTimeInput) - .clearAndType(max); + .clearAndType(max, { force: true }); } export const rateOptions = { diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 649211ae797..3b3a3d9104d 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1520,10 +1520,6 @@ "9FGTOt": { "string": "Allow access to orders of all channels" }, - "9IWg/f": { - "context": "button", - "string": "SETUP END DATE" - }, "9MGrEp": { "string": "Is available" }, @@ -2248,10 +2244,6 @@ "ESDTC/": { "string": "Manage Sales Channel Availability" }, - "ETHnjq": { - "context": "header", - "string": "Private Metadata" - }, "EWCUdP": { "string": "Channels A to Z" }, @@ -4737,10 +4729,6 @@ "VZsE96": { "string": "Collection Name" }, - "VcI+Zh": { - "context": "header", - "string": "Metadata" - }, "VceXrc": { "context": "initial balance filter label", "string": "Initial balance" @@ -5564,6 +5552,10 @@ "bFhzRX": { "string": "No products are out of stock" }, + "bGqAdR": { + "context": "seo complete text", + "string": "Complete" + }, "bHdFph": { "context": "subsection header", "string": "Address" @@ -5651,6 +5643,10 @@ "bj6pTd": { "string": "Something's missing" }, + "bk2M4q": { + "context": "button", + "string": "Cancel end date" + }, "bk9KUX": { "context": "ProductTypeDeleteWarningDialog single consent label", "string": "Yes, I want to delete this product type and assigned products" @@ -5772,10 +5768,6 @@ "cY42ht": { "string": "Password cannot be entirely numeric" }, - "cY6H2C": { - "context": "empty metadata text", - "string": "No metadata created for this element. Use the button below to add new metadata field." - }, "cZN5Jd": { "context": "Webhook details events", "string": "Events" @@ -6077,6 +6069,9 @@ "context": "gift card history message", "string": "Gift card was activated by {activatedBy}" }, + "fLCV/w": { + "string": "Add new value" + }, "fLhj3a": { "context": "gift card history message", "string": "Gift card expiry date was updated" @@ -6273,6 +6268,9 @@ "context": "gift card history message", "string": "Gift card was resent" }, + "gjBiyj": { + "string": "Loading..." + }, "gksZwp": { "context": "button", "string": "Create product type" @@ -6729,6 +6727,10 @@ "context": "total order price", "string": "Total" }, + "kAPaN6": { + "context": "empty metadata text", + "string": "Empty" + }, "kEAjZV": { "context": "rich text attribute type", "string": "Rich Text" @@ -7821,10 +7823,6 @@ "context": "limit voucher", "string": "Limit of Uses" }, - "s5Imt5": { - "context": "button", - "string": "Edit website SEO" - }, "s5v6m0": { "context": "order", "string": "Gift Card ordered" @@ -8045,6 +8043,10 @@ "context": "order status", "string": "Draft" }, + "tqJwfo": { + "context": "button", + "string": "Set end date" + }, "tsL3IW": { "context": "gift card history message", "string": "Gift card was sent to customer" @@ -8237,10 +8239,6 @@ "context": "OrderPayment does not require shipping", "string": "does not apply" }, - "v+Pkm+": { - "context": "field is optional", - "string": "*Optional. Adding product to collection helps users find it." - }, "v/1VA6": { "context": "add order note, button", "string": "Send" @@ -8263,10 +8261,6 @@ "v3WWK+": { "string": "Status is invalid" }, - "v9ILn/": { - "context": "button", - "string": "CANCEL END DATE" - }, "vC8vyb": { "context": "enabled status option label", "string": "Enabled" @@ -8647,6 +8641,10 @@ "y7mfbl": { "string": "Currently, there are no countries assigned to this shipping zone" }, + "y8E0iG": { + "context": "seo incomplete text", + "string": "Incomplete" + }, "y9cvqE": { "context": "button", "string": "Create Voucher" diff --git a/package-lock.json b/package-lock.json index d0cce8fc2a9..18ea24867fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.4", "@reach/auto-id": "^0.16.0", - "@saleor/macaw-ui": "0.8.0-pre.122", + "@saleor/macaw-ui": "0.8.0-pre.133", "@saleor/sdk": "0.6.0", "@sentry/react": "^6.0.0", "@types/faker": "^5.1.6", @@ -3572,11 +3572,11 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", - "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", "dependencies": { - "@floating-ui/dom": "^1.3.0" + "@floating-ui/dom": "^1.5.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -8532,11 +8532,12 @@ } }, "node_modules/@saleor/macaw-ui": { - "version": "0.8.0-pre.122", - "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.122.tgz", - "integrity": "sha512-Ai2t65KUtQXl5Aav1S43R5qTcHXaYW9euIO1WW0cRXXrdtB4sd+YvBW/Y6y5WgMgHAPrWWbw7bqA4Bbpf3IdWQ==", + "version": "0.8.0-pre.133", + "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.133.tgz", + "integrity": "sha512-c9J171RErLa7eJ1DpP/KTC5XktW7iEBZGUapyZMsyUXhuu1j0vSfLZkDVOOf00gHjeW16HbYowdWlck7m9uYeg==", "dependencies": { "@dessert-box/react": "^0.4.0", + "@floating-ui/react-dom": "^2.0.2", "@floating-ui/react-dom-interactions": "^0.5.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", @@ -8549,6 +8550,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.6", "@vanilla-extract/css-utils": "^0.1.3", + "@vanilla-extract/recipes": "^0.5.0", "clsx": "^1.2.1", "downshift": "^6.1.12", "downshift7": "npm:downshift@7.6.0", @@ -13118,6 +13120,14 @@ "resolved": "https://registry.npmjs.org/@vanilla-extract/css-utils/-/css-utils-0.1.3.tgz", "integrity": "sha512-PZAcHROlgtCUGI2y0JntdNwvPwCNyeVnkQu6KTYKdmxBbK3w72XJUmLFYapfaFfgami4I9CTLnrJTPdtmS3gpw==" }, + "node_modules/@vanilla-extract/recipes": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@vanilla-extract/recipes/-/recipes-0.5.0.tgz", + "integrity": "sha512-NfdZ8XyqCDG2RsO3FmYPALDMKg5045dRD97NbBb0Fog3LMDVXZxYgDOct5FAWob8U6W4GbhVpRZt1X9hNnH6fA==", + "peerDependencies": { + "@vanilla-extract/css": "^1.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz", @@ -38752,11 +38762,11 @@ } }, "@floating-ui/react-dom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", - "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", "requires": { - "@floating-ui/dom": "^1.3.0" + "@floating-ui/dom": "^1.5.1" } }, "@floating-ui/react-dom-interactions": { @@ -42190,11 +42200,12 @@ } }, "@saleor/macaw-ui": { - "version": "0.8.0-pre.122", - "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.122.tgz", - "integrity": "sha512-Ai2t65KUtQXl5Aav1S43R5qTcHXaYW9euIO1WW0cRXXrdtB4sd+YvBW/Y6y5WgMgHAPrWWbw7bqA4Bbpf3IdWQ==", + "version": "0.8.0-pre.133", + "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.133.tgz", + "integrity": "sha512-c9J171RErLa7eJ1DpP/KTC5XktW7iEBZGUapyZMsyUXhuu1j0vSfLZkDVOOf00gHjeW16HbYowdWlck7m9uYeg==", "requires": { "@dessert-box/react": "^0.4.0", + "@floating-ui/react-dom": "^2.0.2", "@floating-ui/react-dom-interactions": "^0.5.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", @@ -42207,6 +42218,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.6", "@vanilla-extract/css-utils": "^0.1.3", + "@vanilla-extract/recipes": "^0.5.0", "clsx": "^1.2.1", "downshift": "^6.1.12", "downshift7": "npm:downshift@7.6.0", @@ -45552,6 +45564,11 @@ "resolved": "https://registry.npmjs.org/@vanilla-extract/css-utils/-/css-utils-0.1.3.tgz", "integrity": "sha512-PZAcHROlgtCUGI2y0JntdNwvPwCNyeVnkQu6KTYKdmxBbK3w72XJUmLFYapfaFfgami4I9CTLnrJTPdtmS3gpw==" }, + "@vanilla-extract/recipes": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@vanilla-extract/recipes/-/recipes-0.5.0.tgz", + "integrity": "sha512-NfdZ8XyqCDG2RsO3FmYPALDMKg5045dRD97NbBb0Fog3LMDVXZxYgDOct5FAWob8U6W4GbhVpRZt1X9hNnH6fA==" + }, "@vitejs/plugin-react": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz", diff --git a/package.json b/package.json index 43d5571be11..7f9c9030cb8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.4", "@reach/auto-id": "^0.16.0", - "@saleor/macaw-ui": "0.8.0-pre.122", + "@saleor/macaw-ui": "0.8.0-pre.133", "@saleor/sdk": "0.6.0", "@sentry/react": "^6.0.0", "@types/faker": "^5.1.6", diff --git a/src/attributes/utils/handlers.ts b/src/attributes/utils/handlers.ts index 493b8971ce1..02ffd248465 100644 --- a/src/attributes/utils/handlers.ts +++ b/src/attributes/utils/handlers.ts @@ -41,11 +41,17 @@ export function createAttributeMultiChangeHandler( attributes: FormsetData, triggerChange: () => void, ): FormsetChange { - return (attributeId: string, value: string) => { + return (attributeId: string, value: string | string[]) => { const attribute = attributes.find( attribute => attribute.id === attributeId, ); + if (Array.isArray(value)) { + triggerChange(); + changeAttributeData(attributeId, value); + return; + } + const newAttributeValues = toggle( value, attribute?.value ?? [], diff --git a/src/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard.tsx b/src/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard.tsx index e11fd6bb23b..feedd77ccd1 100644 --- a/src/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard.tsx +++ b/src/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard.tsx @@ -1,9 +1,8 @@ -import { Card, CardContent } from "@material-ui/core"; +import { DashboardCard } from "@dashboard/components/Card"; +import { Text } from "@saleor/macaw-ui/next"; import React from "react"; import { defineMessages, FormattedMessage } from "react-intl"; -import CardTitle from "../../../components/CardTitle"; - const messages = defineMessages({ title: { id: "CT5PAn", @@ -19,12 +18,16 @@ const messages = defineMessages({ }); const CannotDefineChannelsAvailabilityCard: React.FC = () => ( - - } /> - - - - + + + + + + + + + + ); export default CannotDefineChannelsAvailabilityCard; diff --git a/src/components/Attributes/AttributeRow.tsx b/src/components/Attributes/AttributeRow.tsx index 771888bcf56..1f5a76599f2 100644 --- a/src/components/Attributes/AttributeRow.tsx +++ b/src/components/Attributes/AttributeRow.tsx @@ -14,17 +14,14 @@ import { getSingleDisplayValue, } from "@dashboard/components/Attributes/utils"; import FileUploadField from "@dashboard/components/FileUploadField"; -import MultiAutocompleteSelectField from "@dashboard/components/MultiAutocompleteSelectField"; import RichTextEditor from "@dashboard/components/RichTextEditor"; -import SingleAutocompleteSelectField from "@dashboard/components/SingleAutocompleteSelectField"; import SortableChipsField from "@dashboard/components/SortableChipsField"; import { AttributeInputTypeEnum } from "@dashboard/graphql"; -import { commonMessages } from "@dashboard/intl"; -import { TextField } from "@material-ui/core"; -import { Box, Checkbox, Input, Text } from "@saleor/macaw-ui/next"; +import { Box, Input, Text, Toggle } from "@saleor/macaw-ui/next"; import React from "react"; import { useIntl } from "react-intl"; +import { Combobox, Multiselect } from "../Combobox"; import { DateTimeField } from "../DateTimeField"; import { AttributeRowProps } from "./types"; @@ -90,27 +87,32 @@ const AttributeRow: React.FC = ({ ); case AttributeInputTypeEnum.DROPDOWN: return ( - - + onChange(attribute.id, event.target.value)} - allowCustomValues={true} - fetchOnFocus={true} - fetchChoices={value => fetchAttributeValues(value, attribute.id)} + label="" + onChange={e => onChange(attribute.id, e.target.value)} + fetchOptions={query => { + fetchAttributeValues(query, attribute.id); + }} onBlur={onAttributeSelectBlur} - {...fetchMoreAttributeValues} + fetchMore={fetchMoreAttributeValues} /> ); @@ -131,12 +133,11 @@ const AttributeRow: React.FC = ({ onChange(attribute.id, event.target.value)} type="text" @@ -159,7 +160,6 @@ const AttributeRow: React.FC = ({ {getShouldMount(attribute.id) && ( @@ -170,7 +170,7 @@ const AttributeRow: React.FC = ({ name={`attribute:${attribute.label}`} disabled={disabled} error={!!error} - label={intl.formatMessage(attributeRowMessages.valueLabel)} + label="" helperText={getErrorMessage(error, intl)} id={`attribute:${attribute.label}`} /> @@ -181,14 +181,11 @@ const AttributeRow: React.FC = ({ } case AttributeInputTypeEnum.NUMERIC: return ( - + onChange(attribute.id, event.target.value)} @@ -201,56 +198,42 @@ const AttributeRow: React.FC = ({ ); case AttributeInputTypeEnum.BOOLEAN: return ( - - - - onChange(attribute.id, checked)} - checked={JSON.parse(attribute.value[0] ?? "false")} - error={!!error} - id={`attribute:${attribute.label}`} - /> - - {getErrorMessage(error, intl)} - + + + + + onChange(attribute.id, checked)} + pressed={JSON.parse(attribute.value[0] ?? "false")} + id={`attribute:${attribute.label}`} + /> + + {getErrorMessage(error, intl)} + + - - {attribute.label} - - + ); case AttributeInputTypeEnum.DATE: return ( - - + onChange(attribute.id, event.target.value)} type="date" value={attribute.value[0]} - InputLabelProps={{ shrink: true }} /> ); @@ -258,12 +241,10 @@ const AttributeRow: React.FC = ({ return ( onChange(attribute.id, value)} /> @@ -271,21 +252,27 @@ const AttributeRow: React.FC = ({ default: return ( - onMultiChange(attribute.id, event.target.value)} - allowCustomValues={true} - fetchOnFocus={true} - fetchChoices={value => fetchAttributeValues(value, attribute.id)} + options={getMultiChoices(attributeValues)} + value={getMultiDisplayValue(attribute, attributeValues)} + fetchOptions={query => { + fetchAttributeValues(query, attribute.id); + }} + onChange={e => { + onMultiChange( + attribute.id, + e.target.value.map(({ value }) => value), + ); + }} + fetchMore={fetchMoreAttributeValues} onBlur={onAttributeSelectBlur} - {...fetchMoreAttributeValues} /> ); diff --git a/src/components/Attributes/Attributes.tsx b/src/components/Attributes/Attributes.tsx index dce77c0516b..3149b7e581f 100644 --- a/src/components/Attributes/Attributes.tsx +++ b/src/components/Attributes/Attributes.tsx @@ -11,7 +11,7 @@ import { import { FormsetAtomicData } from "@dashboard/hooks/useFormset"; import { FetchMoreProps } from "@dashboard/types"; import { RichTextGetters } from "@dashboard/utils/richText/useMultipleRichText"; -import { Accordion, Box, Divider, Text } from "@saleor/macaw-ui/next"; +import { Accordion, Box, Text } from "@saleor/macaw-ui/next"; import React from "react"; import { defineMessages, FormattedMessage, useIntl } from "react-intl"; @@ -71,32 +71,36 @@ export const Attributes: React.FC = ({ const intl = useIntl(); return ( - - - {title || intl.formatMessage(messages.header)} - + - + - - - - + + + + {title || intl.formatMessage(messages.header)} + + + + + {attributes.length > 0 && (
    - - {attributes.map((attribute, attributeIndex) => ( + {attributes.map(attribute => ( - {attributeIndex > 0 && } + typeof str === "string" ? str.charAt(0).toUpperCase() + str.slice(1) : str; + export const BasicAttributeRow: React.FC = ({ label, description, @@ -19,10 +22,10 @@ export const BasicAttributeRow: React.FC = ({ as="li" justifyContent="space-between" alignItems="center" - paddingY={6} - paddingX={0.5} + paddingY={2} display="grid" gridTemplateColumns={2} + __gridTemplateColumns="1fr 2fr" gap={5} > = ({ gap={1} cursor={clickableLabel ? "pointer" : "auto"} > - {label} + {capitalize(label)} {description && ( diff --git a/src/components/Attributes/ExtendedAttributeRow.tsx b/src/components/Attributes/ExtendedAttributeRow.tsx index 8e23dae0dea..81552e5bfcd 100644 --- a/src/components/Attributes/ExtendedAttributeRow.tsx +++ b/src/components/Attributes/ExtendedAttributeRow.tsx @@ -15,10 +15,12 @@ const ExtendedAttributeRow: React.FC = props => { <> {label} - {children} + + + {children} + ); }; diff --git a/src/components/Attributes/SwatchRow.tsx b/src/components/Attributes/SwatchRow.tsx index 5b765f70fb9..bf03537ce2e 100644 --- a/src/components/Attributes/SwatchRow.tsx +++ b/src/components/Attributes/SwatchRow.tsx @@ -4,14 +4,12 @@ import { getErrorMessage, getSingleDisplayValue, } from "@dashboard/components/Attributes/utils"; -import HorizontalSpacer from "@dashboard/components/HorizontalSpacer"; -import SingleAutocompleteSelectField from "@dashboard/components/SingleAutocompleteSelectField"; import { getBySlug } from "@dashboard/misc"; -import { InputAdornment } from "@material-ui/core"; -import React from "react"; +import { Box } from "@saleor/macaw-ui/next"; +import React, { useMemo } from "react"; import { useIntl } from "react-intl"; -import { useStyles } from "./styles"; +import { Combobox } from "../Combobox"; import { AttributeRowProps } from "./types"; type SwatchRowProps = Pick< @@ -34,62 +32,86 @@ export const SwatchRow: React.FC = ({ error, onChange, }) => { - const classes = useStyles(); const intl = useIntl(); const value = attribute.data.values.find(getBySlug(attribute.value[0])); + const options = useMemo( + () => + attributeValues.map(({ file, value, slug, name }) => ({ + label: name, + value: slug, + startAdornment: ( + + ), + })), + [attributeValues], + ); + return ( - - ({ - label: ( - <> -
    - - {name} - - ), - value: slug, - }))} + + + value ? ( + + ) : null + } error={!!error} + label="" helperText={getErrorMessage(error, intl)} name={`attribute:${attribute.label}`} id={`attribute:${attribute.label}`} - value={attribute.value[0]} - onChange={event => onChange(attribute.id, event.target.value)} - fetchChoices={value => fetchAttributeValues(value, attribute.id)} - InputProps={{ - classes: { input: classes.swatchInput }, - startAdornment: ( - -
    - - ), + onChange={e => onChange(attribute.id, e.target.value)} + fetchOptions={query => { + fetchAttributeValues(query, attribute.id); }} - {...fetchMoreAttributeValues} + fetchMore={fetchMoreAttributeValues} /> ); }; + +const SwatchPreviewBox = ({ + isFile, + backgroundColor, + backgroundImageUrl, +}: { + isFile: boolean; + backgroundImageUrl?: string; + backgroundColor?: string; +}) => { + return ( + + ); +}; diff --git a/src/components/Attributes/messages.ts b/src/components/Attributes/messages.ts index a91d2785419..02b0bfea95e 100644 --- a/src/components/Attributes/messages.ts +++ b/src/components/Attributes/messages.ts @@ -16,4 +16,8 @@ export const attributeRowMessages = defineMessages({ defaultMessage: "Assign references", description: "button label", }, + addNewValue: { + id: "fLCV/w", + defaultMessage: "Add new value", + }, }); diff --git a/src/components/Attributes/types.ts b/src/components/Attributes/types.ts index 5706a76ab90..d48c7d8fca1 100644 --- a/src/components/Attributes/types.ts +++ b/src/components/Attributes/types.ts @@ -18,7 +18,7 @@ export enum VariantAttributeScope { export interface AttributeRowHandlers { onChange: FormsetChange; onFileChange: FormsetChange; - onMultiChange: FormsetChange; + onMultiChange: FormsetChange; onReferencesAddClick: (attribute: AttributeInput) => void; onReferencesRemove: FormsetChange; onReferencesReorder: FormsetChange; diff --git a/src/components/Card/Root.tsx b/src/components/Card/Root.tsx index 1789b3cf893..85bed8b6592 100644 --- a/src/components/Card/Root.tsx +++ b/src/components/Card/Root.tsx @@ -2,7 +2,7 @@ import { Box, Sprinkles } from "@saleor/macaw-ui/next"; import React from "react"; export const Root: React.FC = ({ children, ...rest }) => ( - + {children} ); diff --git a/src/components/ChannelsAvailabilityCard/ChannelsAvailabilityCardWrapper.tsx b/src/components/ChannelsAvailabilityCard/ChannelsAvailabilityCardWrapper.tsx index 99ca0f56ccf..4db72804fa5 100644 --- a/src/components/ChannelsAvailabilityCard/ChannelsAvailabilityCardWrapper.tsx +++ b/src/components/ChannelsAvailabilityCard/ChannelsAvailabilityCardWrapper.tsx @@ -41,7 +41,7 @@ export const ChannelsAvailabilityCardWrapper: React.FC< return ( - +
    {intl.formatMessage({ @@ -51,7 +51,9 @@ export const ChannelsAvailabilityCardWrapper: React.FC< })}
    {!!channelsAvailabilityText && ( - {channelsAvailabilityText} + + {channelsAvailabilityText} + )}
    diff --git a/src/components/Combobox/components/Combobox.tsx b/src/components/Combobox/components/Combobox.tsx new file mode 100644 index 00000000000..85a9ccc0a44 --- /dev/null +++ b/src/components/Combobox/components/Combobox.tsx @@ -0,0 +1,88 @@ +import { ChangeEvent } from "@dashboard/hooks/useForm"; +import { commonMessages } from "@dashboard/intl"; +import { FetchMoreProps } from "@dashboard/types"; +import { + DynamicCombobox, + DynamicComboboxProps, + Option, +} from "@saleor/macaw-ui/next"; +import React, { useEffect, useRef, useState } from "react"; +import { useIntl } from "react-intl"; + +import { useCombbobxCustomOption } from "../hooks/useCombbobxCustomOption"; +import { useComboboxHandlers } from "../hooks/useComboboxHandlers"; + +type ComboboxProps = Omit< + DynamicComboboxProps
    } - /> + + {title} + + {children} + + ); }; diff --git a/src/components/DateTimeField/DateTimeField.tsx b/src/components/DateTimeField/DateTimeField.tsx index 8b57aa986e5..f555d67fcb3 100644 --- a/src/components/DateTimeField/DateTimeField.tsx +++ b/src/components/DateTimeField/DateTimeField.tsx @@ -6,17 +6,17 @@ import { } from "@dashboard/graphql"; import { commonMessages } from "@dashboard/intl"; import { joinDateTime, splitDateTime } from "@dashboard/misc"; -import { TextField } from "@material-ui/core"; -import { TextFieldProps } from "@material-ui/core/TextField"; -import { Box } from "@saleor/macaw-ui/next"; +import { Box, Input } from "@saleor/macaw-ui/next"; import React from "react"; import { useIntl } from "react-intl"; -type DateTimeFieldProps = Omit & { +interface DateTimeFieldProps { onChange: (value: string) => void; error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment; value: string; -}; + disabled: boolean; + name: string; +} export const DateTimeField: React.FC = ({ disabled, @@ -31,8 +31,9 @@ export const DateTimeField: React.FC = ({ return ( - = ({ }} type="date" value={parsedValue.date} - InputLabelProps={{ shrink: true }} /> - = ({ }} type="time" value={parsedValue.time} - InputLabelProps={{ shrink: true }} /> ); diff --git a/src/components/DateTimeTimezoneField.tsx b/src/components/DateTimeTimezoneField.tsx index 7de12f29d5b..770ca8fe2d7 100644 --- a/src/components/DateTimeTimezoneField.tsx +++ b/src/components/DateTimeTimezoneField.tsx @@ -1,33 +1,22 @@ // @ts-strict-ignore import { commonMessages } from "@dashboard/intl"; import { DateTime, joinDateTime, splitDateTime } from "@dashboard/misc"; -import { TextField } from "@material-ui/core"; -import { TextFieldProps } from "@material-ui/core/TextField"; -import { makeStyles } from "@saleor/macaw-ui"; +import { Box, Input, sprinkles } from "@saleor/macaw-ui/next"; import React, { useEffect, useState } from "react"; import { useIntl } from "react-intl"; import ErrorNoticeBar from "./ErrorNoticeBar"; -type DateTimeFieldProps = Omit & { +interface DateTimeFieldProps { onChange: (value: string) => void; error: string | React.ReactNode; setError?: () => void; futureDatesOnly?: boolean; value: string; -}; - -const useStyles = makeStyles( - theme => ({ - dateInput: { - marginRight: theme.spacing(2), - }, - errorNoticeBar: { - marginTop: theme.spacing(2), - }, - }), - { name: "DateTimeTimezoneField" }, -); + disabled?: boolean; + fullWidth?: boolean; + name: string; +} export const DateTimeTimezoneField: React.FC = ({ disabled, @@ -37,7 +26,6 @@ export const DateTimeTimezoneField: React.FC = ({ fullWidth, value: initialValue, }) => { - const classes = useStyles({}); const intl = useIntl(); const [value, setValue] = useState( initialValue ? splitDateTime(initialValue) : { date: "", time: "" }, @@ -50,38 +38,43 @@ export const DateTimeTimezoneField: React.FC = ({ return ( <> - { - const date = event.target.value; - setValue(value => ({ ...value, date })); - }} - type="date" - value={value.date} - InputLabelProps={{ shrink: true }} - /> - { - const time = event.target.value; - setValue(value => ({ ...value, time })); - }} - type="time" - value={value.time} - InputLabelProps={{ shrink: true }} - /> + + { + const date = event.target.value; + setValue(value => ({ ...value, date })); + }} + type="date" + value={value.date} + /> + { + const time = event.target.value; + setValue(value => ({ ...value, time })); + }} + type="time" + value={value.time} + /> + {error && ( - + )} ); diff --git a/src/components/FileUploadField/FileUploadField.tsx b/src/components/FileUploadField/FileUploadField.tsx index c3b07d485c7..9c6ee63ed2d 100644 --- a/src/components/FileUploadField/FileUploadField.tsx +++ b/src/components/FileUploadField/FileUploadField.tsx @@ -56,7 +56,7 @@ const FileUploadField: React.FC = props => { return ( <> - + {file.label ? ( @@ -89,7 +89,7 @@ const FileUploadField: React.FC = props => { )} {error && ( - + {helperText} )} diff --git a/src/components/Layouts/Detail/RightSidebar.tsx b/src/components/Layouts/Detail/RightSidebar.tsx index e8084d12847..c7c210dc50c 100644 --- a/src/components/Layouts/Detail/RightSidebar.tsx +++ b/src/components/Layouts/Detail/RightSidebar.tsx @@ -17,6 +17,7 @@ export const RightSidebar: React.FC = ({ borderLeftWidth={1} gridColumn={"8"} gridRow={{ mobile: "6", tablet: "full", desktop: "full" }} + paddingBottom={6} > {children} diff --git a/src/components/MediaTile/MediaTile.tsx b/src/components/MediaTile/MediaTile.tsx index 95cee8ef533..24e8ab5f6ba 100644 --- a/src/components/MediaTile/MediaTile.tsx +++ b/src/components/MediaTile/MediaTile.tsx @@ -1,6 +1,7 @@ import { IconButton } from "@dashboard/components/IconButton"; import { CircularProgress } from "@material-ui/core"; import { DeleteIcon, EditIcon, makeStyles } from "@saleor/macaw-ui"; +import { vars } from "@saleor/macaw-ui/next"; import clsx from "clsx"; import React from "react"; @@ -23,7 +24,7 @@ const useStyles = makeStyles( borderRadius: theme.spacing(), height: 148, overflow: "hidden", - padding: theme.spacing(2), + padding: vars.spacing[1], position: "relative", width: 148, }, diff --git a/src/components/Metadata/Metadata.test.tsx b/src/components/Metadata/Metadata.test.tsx index f520196b30c..5c4f3085dcb 100644 --- a/src/components/Metadata/Metadata.test.tsx +++ b/src/components/Metadata/Metadata.test.tsx @@ -24,14 +24,14 @@ describe("Metadata editor", () => { // Arrange render(); const user = userEvent.setup(); - const isExpandedAttribute = "data-test-expanded"; - const editor = screen.getAllByTestId("metadata-editor")[0]; + const isExpandedAttribute = "data-state"; + const editor = screen.getAllByTestId("metadata-item")[0]; // Assert - expect(editor).toHaveAttribute(isExpandedAttribute, "false"); + expect(editor).toHaveAttribute(isExpandedAttribute, "closed"); // Act await user.click(getFirstExpandIcon()); // Assert - expect(editor).toHaveAttribute(isExpandedAttribute, "true"); + expect(editor).toHaveAttribute(isExpandedAttribute, "open"); }); xit("can edit field name", async () => { diff --git a/src/components/Metadata/Metadata.tsx b/src/components/Metadata/Metadata.tsx index 917b97ab262..e0cf7021cfa 100644 --- a/src/components/Metadata/Metadata.tsx +++ b/src/components/Metadata/Metadata.tsx @@ -69,7 +69,7 @@ export const Metadata: React.FC = memo(({ data, onChange }) => { }; return ( - + ({ - input: { - padding: theme.spacing(0.5, 2), - }, - nameInput: { - padding: "13px 16px", - }, - }), - { - name: "Metadata", - }, -); export interface MetadataCardProps { data: MetadataInput[]; @@ -52,196 +20,92 @@ export const MetadataCard: React.FC = ({ onChange, }) => { const intl = useIntl(); - const [expanded, setExpanded] = React.useState(false); - const classes = useStyles(); + const title = isPrivate + ? { + id: "ETHnjq", + defaultMessage: "Private Metadata", + description: "header", + } + : { + id: "VcI+Zh", + defaultMessage: "Metadata", + description: "header", + }; return ( - - setExpanded(!expanded)} - cursor="pointer" - > - {isPrivate - ? intl.formatMessage({ - id: "ETHnjq", - defaultMessage: "Private Metadata", - description: "header", - }) - : intl.formatMessage({ - id: "VcI+Zh", - defaultMessage: "Metadata", - description: "header", - })} - + )} - - - - - )} - - )} + + + + ); }; diff --git a/src/components/Metadata/MetadataCardTable.tsx b/src/components/Metadata/MetadataCardTable.tsx new file mode 100644 index 00000000000..de04fb95002 --- /dev/null +++ b/src/components/Metadata/MetadataCardTable.tsx @@ -0,0 +1,122 @@ +import TableRowLink from "@dashboard/components/TableRowLink"; +import { MetadataInput } from "@dashboard/graphql"; +import { FormChange } from "@dashboard/hooks/useForm"; +import { Table, TableBody, TableCell, TableHead } from "@material-ui/core"; +import { + Box, + Button, + Input, + Text, + Textarea, + TrashBinIcon, + vars, +} from "@saleor/macaw-ui/next"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { EventDataAction } from "./types"; +import { nameInputPrefix, nameSeparator, valueInputPrefix } from "./utils"; + +interface MetadataCardTableProps { + data: MetadataInput[]; + onChange: FormChange; +} + +export const MetadataCardTable = ({ + data, + onChange, +}: MetadataCardTableProps) => { + if (!data || data.length === 0) { + return null; + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + {data.map((field, fieldIndex) => ( + + + + + + +