From ebe56da5835f94b41aeb5d20227bcb0ddfb7efab Mon Sep 17 00:00:00 2001 From: Piotr Grundas Date: Tue, 21 Sep 2021 15:16:21 +0200 Subject: [PATCH] Add swatch attributes (#1301) * Initial work * # * Base color logic * Handle file urls * Improvements, fix snapshots * Add messages * Update changelog * Update storybook * Improvements * Messages * Review corrections * Bugfixes --- CHANGELOG.md | 1 + locale/defaultMessages.json | 24 +- package-lock.json | 206 ++++++++- package.json | 5 + schema.graphql | 17 +- .../AttributeDetails/AttributeDetails.tsx | 4 + .../components/AttributeDetails/messages.tsx | 4 + .../AttributePage/AttributePage.tsx | 156 ++++--- .../AttributeSwatchField.tsx | 121 +++++ .../components/AttributeSwatchField/index.ts | 2 + .../AttributeSwatchField/messages.ts | 12 + .../components/AttributeSwatchField/styles.ts | 14 + .../AttributeValueEditDialog.tsx | 34 +- .../AttributeValues/AttributeValues.tsx | 50 +- src/attributes/fixtures.ts | 6 +- src/attributes/mutations.ts | 2 +- src/attributes/types/AttributeDetails.ts | 1 + src/attributes/types/AttributeValueCreate.ts | 1 + src/attributes/types/AttributeValueDelete.ts | 1 + src/attributes/types/AttributeValueUpdate.ts | 5 +- src/attributes/utils/data.ts | 55 ++- src/attributes/utils/handlers.test.ts | 15 +- .../views/AttributeCreate/AttributeCreate.tsx | 180 ++++---- .../AttributeDetails/AttributeDetails.tsx | 263 +++++------ src/components/Attributes/AttributeRow.tsx | 84 +--- src/components/Attributes/Attributes.tsx | 4 +- src/components/Attributes/SwatchRow.tsx | 90 ++++ src/components/Attributes/fixtures.ts | 73 ++- src/components/Attributes/messages.ts | 16 + src/components/Attributes/styles.ts | 25 + src/components/Attributes/types.ts | 29 ++ .../ColorPicker/ColorPicker.stories.tsx | 19 + src/components/ColorPicker/ColorPicker.tsx | 153 ++++++ src/components/ColorPicker/index.ts | 1 + .../SingleAutocompleteSelectField.tsx | 4 +- .../SingleAutocompleteSelectFieldContent.tsx | 18 +- src/fragments/attributes.ts | 1 + src/fragments/types/AttributeValueFragment.ts | 1 + .../types/AttributeValueListFragment.ts | 1 + src/fragments/types/PageAttributesFragment.ts | 3 + src/fragments/types/PageDetailsFragment.ts | 3 + src/fragments/types/Product.ts | 3 + src/fragments/types/ProductVariant.ts | 8 +- .../types/ProductVariantAttributesFragment.ts | 3 + .../types/SelectedVariantAttributeFragment.ts | 2 + .../types/VariantAttributeFragment.ts | 1 + src/pages/fixtures.ts | 48 +- src/pages/types/PageDetails.ts | 3 + src/pages/types/PageType.ts | 1 + src/pages/types/PageUpdate.ts | 3 + .../ProductExportDialogInfo.tsx | 2 +- .../__snapshots__/reducer.test.ts.snap | 48 ++ .../ProductVariantCreatorPage/fixtures.ts | 12 +- src/products/fixtures.ts | 123 +++-- .../types/CreateMultipleVariantsData.ts | 3 + src/products/types/ProductDetails.ts | 3 + src/products/types/ProductList.ts | 1 + src/products/types/ProductType.ts | 1 + src/products/types/ProductUpdate.ts | 3 + .../types/ProductVariantCreateData.ts | 2 + src/products/types/ProductVariantDetails.ts | 4 + src/products/types/SimpleProductUpdate.ts | 19 + src/products/types/VariantCreate.ts | 4 + src/products/types/VariantUpdate.ts | 8 + src/searches/types/SearchAttributeValues.ts | 1 + .../__snapshots__/Stories.test.ts.snap | 434 +++++++++++++++++- .../stories/attributes/AttributePage.tsx | 27 +- src/types/globalTypes.ts | 15 +- 68 files changed, 2001 insertions(+), 485 deletions(-) create mode 100644 src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx create mode 100644 src/attributes/components/AttributeSwatchField/index.ts create mode 100644 src/attributes/components/AttributeSwatchField/messages.ts create mode 100644 src/attributes/components/AttributeSwatchField/styles.ts create mode 100644 src/components/Attributes/SwatchRow.tsx create mode 100644 src/components/Attributes/messages.ts create mode 100644 src/components/Attributes/styles.ts create mode 100644 src/components/ColorPicker/ColorPicker.stories.tsx create mode 100644 src/components/ColorPicker/ColorPicker.tsx create mode 100644 src/components/ColorPicker/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d103b89d5..acabe1748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ All notable, unreleased changes to this project will be documented in this file. - Fixed navigation menu items reordering issue - #1239 by @kamilpastuszka - Fixed issue with modals containing invalid, redundant scrolls #1240 by @kamilpastuszka - Add text attribute for product and page translations - #1276 by @kamilpastuszka +- Add swatch attributes - #1301 by @piotrgrundas - Add app dashboard extensions - #1292 by @jwm0 - Introduce fulfillment creation - #1241 by @orzechdev - Introduce Click&Collect feature - #1268 by @kuchichan diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 0edb01452..a238a31f0 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1021,6 +1021,10 @@ "context": "check to require numeric attribute unit", "string": "Select unit" }, + "src_dot_attributes_dot_components_dot_AttributeDetails_dot_swatch": { + "context": "swatch attribute type", + "string": "Swatch" + }, "src_dot_attributes_dot_components_dot_AttributeDetails_dot_text": { "context": "text attribute type", "string": "Text" @@ -1168,6 +1172,14 @@ "context": "caption", "string": "If enabled, attribute will be accessible to customers." }, + "src_dot_attributes_dot_components_dot_AttributeSwatchField_dot_image": { + "context": "swatch attribute image label", + "string": "Image" + }, + "src_dot_attributes_dot_components_dot_AttributeSwatchField_dot_picker": { + "context": "swatch attribute color picker label", + "string": "Picker" + }, "src_dot_attributes_dot_components_dot_AttributeValueDeleteDialog_dot_1326420604": { "context": "delete attribute value", "string": "Are you sure you want to delete \"{name}\" value?" @@ -1211,6 +1223,10 @@ "context": "assign attribute value button", "string": "Assign value" }, + "src_dot_attributes_dot_components_dot_AttributeValues_dot_4086835540": { + "context": "attribute values list: slug column header", + "string": "Swatch" + }, "src_dot_attributes_dot_views_dot_AttributeCreate_dot_11941964": { "string": "Successfully created attribute" }, @@ -1914,10 +1930,6 @@ "src_dot_components_dot_AttributeUnassignDialog_dot_2037985699": { "string": "Are you sure you want to unassign {attributeName} from {itemTypeName}?" }, - "src_dot_components_dot_Attributes_dot_3824528779": { - "context": "button label", - "string": "Assign references" - }, "src_dot_components_dot_Attributes_dot_attributesNumber": { "context": "number of attributes", "string": "{number} Attributes" @@ -1930,6 +1942,10 @@ "context": "attribute values", "string": "Values" }, + "src_dot_components_dot_Attributes_dot_reference": { + "context": "button label", + "string": "Assign references" + }, "src_dot_components_dot_Attributes_dot_valueLabel": { "context": "attribute value", "string": "Value" diff --git a/package-lock.json b/package-lock.json index 2400945fc..f36014fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6214,6 +6214,21 @@ "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==", "dev": true }, + "@types/color-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz", + "integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==", + "dev": true, + "requires": { + "@types/color-name": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/enzyme": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.8.tgz", @@ -7024,6 +7039,193 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@uiw/color-convert": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-0.0.34.tgz", + "integrity": "sha512-GgF8iks/Xk6N/vEQ2gTmuKwgzvgaoCwOR7uxBV7F2bkMUFNLFxWiG80Ze8sb6o6O2ISA2ATD3Dp+bhgRW2GPIw==" + }, + "@uiw/react-color-alpha": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-0.0.34.tgz", + "integrity": "sha512-dpT4N5QpPf9EmleO9+tcJRGr0eQUjB6U9JSLlOi/wzblTp/JoBNcovb0KuL5I+NdgebLC/3rabYjN7Fb8ZoMPw==", + "requires": { + "@babel/runtime": "7.14.6", + "@uiw/color-convert": "^0.0.34", + "@uiw/react-drag-event-interactive": "^0.0.34" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-color-editable-input": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-0.1.0.tgz", + "integrity": "sha512-+mSLg2vhE+CcqwUR0Avn6RI8+uGCJsKaUdkMYb+n+eKrXe/4gsSPMBHZHlPBWfxORTnwtmVS7mqL9X1qE5STMg==", + "requires": { + "@babel/runtime": "7.14.6" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-color-editable-input-rgba": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-rgba/-/react-color-editable-input-rgba-0.1.0.tgz", + "integrity": "sha512-spT38w17wqXF9MpW8Jy7p0Pr+GIUTEmygKUoI/cOc8XAbpgB+MAPoEUVL/IwGMGy3q4qsN93OE2rbD3hTsKBCw==", + "requires": { + "@babel/runtime": "7.14.6", + "@uiw/color-convert": "^0.1.0", + "@uiw/react-color-editable-input": "^0.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@uiw/color-convert": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-0.1.0.tgz", + "integrity": "sha512-z5CMHakSxH/WQh53F/ncRSVjkaC1in1oS30vJLPozqcYVReCwILomSVQn75shsvurUGcBKhPl2jK9jEEeKn7AA==" + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-color-hue": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-0.0.34.tgz", + "integrity": "sha512-tDwkIyS+BBkjr4u1Pdxss1TIIwdCXciYQhErC4f0cewfE3o4/oiccJxP0gmYol9qceDXWjVgHGONAE23vz/Dkg==", + "requires": { + "@babel/runtime": "7.14.6", + "@uiw/color-convert": "^0.0.34", + "@uiw/react-color-alpha": "^0.0.34" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-color-material": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@uiw/react-color-material/-/react-color-material-0.1.0.tgz", + "integrity": "sha512-0CFQG6gjFeBeqOA+tLkvVY2xZD1hbmysDbPZ1rOLIliF7jS+nnbRefo+bfmnzMKUiyjpgEqUE+VvwJDPrfhgZA==", + "requires": { + "@babel/runtime": "7.14.6", + "@uiw/color-convert": "^0.1.0", + "@uiw/react-color-editable-input": "^0.1.0", + "@uiw/react-color-editable-input-rgba": "^0.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@uiw/color-convert": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-0.1.0.tgz", + "integrity": "sha512-z5CMHakSxH/WQh53F/ncRSVjkaC1in1oS30vJLPozqcYVReCwILomSVQn75shsvurUGcBKhPl2jK9jEEeKn7AA==" + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-color-saturation": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-0.0.34.tgz", + "integrity": "sha512-d4VeVLRw44Z0aOdkrFsy1qXasxM/znW4VPxsaPtA+MDBuxat7meFtI2vbQKlrNrwXZX0I6BASAVBaQO++Eutlg==", + "requires": { + "@babel/runtime": "7.14.6", + "@uiw/color-convert": "^0.0.34", + "@uiw/react-drag-event-interactive": "^0.0.34" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, + "@uiw/react-drag-event-interactive": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-0.0.34.tgz", + "integrity": "sha512-tsHyBlOoD03GEN5zOzmEtfccXKwFge2FFmF9HG2yaa8cqrN1FPczfBDFUeQuEjaD+pFS+p7Jn5y2/uCfhzM5Iw==", + "requires": { + "@babel/runtime": "7.14.6" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -11232,7 +11434,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -11240,8 +11441,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colorette": { "version": "1.2.2", diff --git a/package.json b/package.json index 5d9a0c223..6909b1b73 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,9 @@ "@saleor/macaw-ui": "^0.2.3", "@sentry/react": "^6.0.0", "@types/faker": "^5.1.6", + "@uiw/react-color-hue": "0.0.34", + "@uiw/react-color-material": "^0.1.0", + "@uiw/react-color-saturation": "0.0.34", "apollo": "^2.32.5", "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", @@ -40,6 +43,7 @@ "apollo-link-error": "^1.1.11", "apollo-upload-client": "^9.1.0", "classnames": "^2.2.6", + "color-convert": "^2.0.1", "crc-32": "^1.2.0", "currency-codes": "^2.1.0", "cypress-mailhog": "^1.3.0", @@ -108,6 +112,7 @@ "@storybook/react": "^5.1.9", "@testing-library/react-hooks": "^1.1.0", "@types/classnames": "^2.2.9", + "@types/color-convert": "^2.0.0", "@types/enzyme": "^3.10.8", "@types/fuzzaldrin": "^2.1.2", "@types/jest": "^24.0.24", diff --git a/schema.graphql b/schema.graphql index 0bdc6a9e1..3f25cc506 100644 --- a/schema.graphql +++ b/schema.graphql @@ -585,6 +585,7 @@ enum AttributeInputTypeEnum { REFERENCE NUMERIC RICH_TEXT + SWATCH BOOLEAN DATE DATE_TIME @@ -648,7 +649,7 @@ input AttributeUpdateInput { slug: String unit: MeasurementUnitsEnum removeValues: [ID] - addValues: [AttributeValueCreateInput] + addValues: [AttributeValueUpdateInput] valueRequired: Boolean isVariantOnly: Boolean visibleInStorefront: Boolean @@ -698,9 +699,11 @@ type AttributeValueCreate { } input AttributeValueCreateInput { - name: String! value: String richText: JSONString + fileUrl: String + contentType: String + name: String! } type AttributeValueDelete { @@ -759,6 +762,14 @@ type AttributeValueUpdate { attributeValue: AttributeValue } +input AttributeValueUpdateInput { + value: String + richText: JSONString + fileUrl: String + contentType: String + name: String +} + input BulkAttributeValueInput { id: ID values: [String!] @@ -3909,7 +3920,7 @@ type Mutation { attributeValueBulkDelete(ids: [ID]!): AttributeValueBulkDelete attributeValueCreate(attribute: ID!, input: AttributeValueCreateInput!): AttributeValueCreate attributeValueDelete(id: ID!): AttributeValueDelete - attributeValueUpdate(id: ID!, input: AttributeValueCreateInput!): AttributeValueUpdate + attributeValueUpdate(id: ID!, input: AttributeValueUpdateInput!): AttributeValueUpdate attributeValueTranslate(id: ID!, input: AttributeValueTranslationInput!, languageCode: LanguageCodeEnum!): AttributeValueTranslate attributeReorderValues(attributeId: ID!, moves: [ReorderInput]!): AttributeReorderValues appCreate(input: AppInput!): AppCreate diff --git a/src/attributes/components/AttributeDetails/AttributeDetails.tsx b/src/attributes/components/AttributeDetails/AttributeDetails.tsx index 85da11af3..e521d367f 100644 --- a/src/attributes/components/AttributeDetails/AttributeDetails.tsx +++ b/src/attributes/components/AttributeDetails/AttributeDetails.tsx @@ -108,6 +108,10 @@ const AttributeDetails: React.FC = props => { { label: intl.formatMessage(inputTypeMessages.dateTime), value: AttributeInputTypeEnum.DATE_TIME + }, + { + label: intl.formatMessage(inputTypeMessages.swatch), + value: AttributeInputTypeEnum.SWATCH } ]; const entityTypeChoices = [ diff --git a/src/attributes/components/AttributeDetails/messages.tsx b/src/attributes/components/AttributeDetails/messages.tsx index 454ad896f..2cac85744 100644 --- a/src/attributes/components/AttributeDetails/messages.tsx +++ b/src/attributes/components/AttributeDetails/messages.tsx @@ -81,6 +81,10 @@ export const inputTypeMessages = defineMessages({ dateTime: { defaultMessage: "Date Time", description: "date time attribute type" + }, + swatch: { + defaultMessage: "Swatch", + description: "swatch attribute type" } }); diff --git a/src/attributes/components/AttributePage/AttributePage.tsx b/src/attributes/components/AttributePage/AttributePage.tsx index 949ea3c9b..213c21c9f 100644 --- a/src/attributes/components/AttributePage/AttributePage.tsx +++ b/src/attributes/components/AttributePage/AttributePage.tsx @@ -54,6 +54,7 @@ export interface AttributePageProps { }; onNextPage: () => void; onPreviousPage: () => void; + children: (data: AttributePageFormData) => React.ReactNode; } export interface AttributePageFormData extends MetadataFormData { @@ -88,7 +89,8 @@ const AttributePage: React.FC = ({ onUpdateListSettings, pageInfo, onNextPage, - onPreviousPage + onPreviousPage, + children }) => { const intl = useIntl(); const { @@ -168,80 +170,84 @@ const AttributePage: React.FC = ({ const changeMetadata = makeMetadataChangeHandler(change); return ( - - - {intl.formatMessage(sectionNames.attributes)} - - attribute.name) - } - /> - -
- - {ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes( - data.inputType - ) && ( - <> - - - - )} - - -
-
- - - -
-
- -
+ <> + + + {intl.formatMessage(sectionNames.attributes)} + + attribute.name) + } + /> + +
+ + {ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes( + data.inputType + ) && ( + <> + + + + )} + + +
+
+ + + +
+
+ +
+ {children(data)} + ); }} diff --git a/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx b/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx new file mode 100644 index 000000000..346941607 --- /dev/null +++ b/src/attributes/components/AttributeSwatchField/AttributeSwatchField.tsx @@ -0,0 +1,121 @@ +import VerticalSpacer from "@saleor/apps/components/VerticalSpacer"; +import { inputTypeMessages } from "@saleor/attributes/components/AttributeDetails/messages"; +import { AttributeValueEditDialogFormData } from "@saleor/attributes/utils/data"; +import { ColorPicker } from "@saleor/components/ColorPicker"; +import FileUploadField from "@saleor/components/FileUploadField"; +import { RadioGroupField } from "@saleor/components/RadioGroupField"; +import { useFileUploadMutation } from "@saleor/files/mutations"; +import { UseFormResult } from "@saleor/hooks/useForm"; +import useNotifier from "@saleor/hooks/useNotifier"; +import { errorMessages } from "@saleor/intl"; +import React, { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { swatchFieldMessages } from "./messages"; +import { useStyles } from "./styles"; + +type AttributeSwatchFieldProps = Pick< + UseFormResult, + "setError" | "set" | "errors" | "clearErrors" | "data" +>; + +type SwatchType = "picker" | "image"; + +const AttributeSwatchField: React.FC> = ({ set, ...props }) => { + const { data } = props; + const notify = useNotifier(); + const intl = useIntl(); + const { formatMessage } = useIntl(); + const classes = useStyles(); + const [processing, setProcessing] = useState(false); + const [uploadFile] = useFileUploadMutation({}); + const [type, setType] = useState( + data.fileUrl ? "image" : "picker" + ); + + const handleColorChange = (hex: string) => + set({ value: hex, fileUrl: undefined, contentType: undefined }); + + const handleFileUpload = async (file: File) => { + setProcessing(true); + + const { + data: { fileUpload } + } = await uploadFile({ variables: { file } }); + + if (fileUpload.errors?.length) { + notify({ + status: "error", + title: intl.formatMessage(errorMessages.imgageUploadErrorTitle), + text: intl.formatMessage(errorMessages.imageUploadErrorText) + }); + } else { + set({ + fileUrl: fileUpload.uploadedFile.url, + contentType: fileUpload.uploadedFile.contentType, + value: undefined + }); + } + + setProcessing(false); + }; + + const handleFileDelete = () => + set({ + fileUrl: undefined, + contentType: undefined, + value: undefined + }); + + return ( + <> + + } + name="swatch" + value={type} + onChange={event => setType(event.target.value)} + data-test="swatch-radio" + /> + {type === "image" ? ( + <> + + + {data.fileUrl && ( +
+ )} + + ) : ( + + )} + + ); +}; + +AttributeSwatchField.displayName = "AttributeSwatchField"; +export default AttributeSwatchField; diff --git a/src/attributes/components/AttributeSwatchField/index.ts b/src/attributes/components/AttributeSwatchField/index.ts new file mode 100644 index 000000000..cb5c8fb36 --- /dev/null +++ b/src/attributes/components/AttributeSwatchField/index.ts @@ -0,0 +1,2 @@ +export { default } from "./AttributeSwatchField"; +export * from "./AttributeSwatchField"; diff --git a/src/attributes/components/AttributeSwatchField/messages.ts b/src/attributes/components/AttributeSwatchField/messages.ts new file mode 100644 index 000000000..e3f583b1c --- /dev/null +++ b/src/attributes/components/AttributeSwatchField/messages.ts @@ -0,0 +1,12 @@ +import { defineMessages } from "react-intl"; + +export const swatchFieldMessages = defineMessages({ + image: { + defaultMessage: "Image", + description: "swatch attribute image label" + }, + picker: { + defaultMessage: "Picker", + description: "swatch attribute color picker label" + } +}); diff --git a/src/attributes/components/AttributeSwatchField/styles.ts b/src/attributes/components/AttributeSwatchField/styles.ts new file mode 100644 index 000000000..6de0ec8a5 --- /dev/null +++ b/src/attributes/components/AttributeSwatchField/styles.ts @@ -0,0 +1,14 @@ +import { makeStyles } from "@saleor/macaw-ui"; + +export const useStyles = makeStyles( + theme => ({ + filePreview: { + marginTop: theme.spacing(3), + width: 216, + height: 216, + backgroundSize: "cover", + backgroundPosition: "center" + } + }), + { name: "AttributeSwatchField" } +); diff --git a/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx b/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx index 73554b810..47aed155d 100644 --- a/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx +++ b/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx @@ -14,14 +14,14 @@ import Form from "@saleor/components/Form"; import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment"; import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; import { buttonMessages } from "@saleor/intl"; -import { maybe } from "@saleor/misc"; +import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { getFormErrors } from "@saleor/utils/errors"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -export interface AttributeValueEditDialogFormData { - name: string; -} +import { AttributeValueEditDialogFormData } from "../../utils/data"; +import AttributeSwatchField from "../AttributeSwatchField"; + export interface AttributeValueEditDialogProps { attributeValue: AttributeValueEditDialogFormData | null; confirmButtonState: ConfirmButtonTransitionState; @@ -30,6 +30,7 @@ export interface AttributeValueEditDialogProps { open: boolean; onSubmit: (data: AttributeValueEditDialogFormData) => void; onClose: () => void; + inputType?: AttributeInputTypeEnum; } const AttributeValueEditDialog: React.FC = ({ @@ -39,14 +40,24 @@ const AttributeValueEditDialog: React.FC = ({ errors: apiErrors, onClose, onSubmit, - open + open, + inputType }) => { const intl = useIntl(); + const attributeValueFields = attributeValue?.fileUrl + ? { + fileUrl: attributeValue?.fileUrl, + contentType: attributeValue?.contentType + } + : { value: attributeValue?.value ?? "" }; + const initialForm: AttributeValueEditDialogFormData = { - name: maybe(() => attributeValue.name, "") + name: attributeValue?.name ?? "", + ...attributeValueFields }; const errors = useModalDialogErrors(apiErrors, open); const formErrors = getFormErrors(["name"], errors); + const isSwatch = inputType === AttributeInputTypeEnum.SWATCH; return ( @@ -64,7 +75,7 @@ const AttributeValueEditDialog: React.FC = ({ )}
- {({ change, data, submit }) => ( + {({ errors, set, change, clearErrors, setError, data, submit }) => ( <> = ({ value={data.name} onChange={change} /> + {isSwatch && ( + + )}
+
+
+
+
+ Swatch Attribute +
+
+
+
+
+ +
+
+
@@ -670,7 +751,7 @@ exports[`Storyshots Attributes / Attributes disabled 1`] = `
- 9 Attributes + 10 Attributes