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
This commit is contained in:
Piotr Grundas 2021-09-21 15:16:21 +02:00 committed by GitHub
parent ffa065d301
commit ebe56da583
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 2001 additions and 485 deletions

View file

@ -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

View file

@ -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"

206
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -108,6 +108,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = props => {
{
label: intl.formatMessage(inputTypeMessages.dateTime),
value: AttributeInputTypeEnum.DATE_TIME
},
{
label: intl.formatMessage(inputTypeMessages.swatch),
value: AttributeInputTypeEnum.SWATCH
}
];
const entityTypeChoices = [

View file

@ -81,6 +81,10 @@ export const inputTypeMessages = defineMessages({
dateTime: {
defaultMessage: "Date Time",
description: "date time attribute type"
},
swatch: {
defaultMessage: "Swatch",
description: "swatch attribute type"
}
});

View file

@ -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<AttributePageProps> = ({
onUpdateListSettings,
pageInfo,
onNextPage,
onPreviousPage
onPreviousPage,
children
}) => {
const intl = useIntl();
const {
@ -168,80 +170,84 @@ const AttributePage: React.FC<AttributePageProps> = ({
const changeMetadata = makeMetadataChangeHandler(change);
return (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.attributes)}
</Backlink>
<PageHeader
title={
attribute === null
? intl.formatMessage({
defaultMessage: "Create New Attribute",
description: "page title"
})
: maybe(() => attribute.name)
}
/>
<Grid>
<div>
<AttributeDetails
canChangeType={attribute === null}
data={data}
disabled={disabled}
apiErrors={apiErrors}
onChange={change}
set={set}
errors={errors}
setError={setError}
clearErrors={clearErrors}
/>
{ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(
data.inputType
) && (
<>
<CardSpacer />
<AttributeValues
disabled={disabled}
values={mapEdgesToItems(values)}
onValueAdd={onValueAdd}
onValueDelete={onValueDelete}
onValueReorder={onValueReorder}
onValueUpdate={onValueUpdate}
settings={settings}
onUpdateListSettings={onUpdateListSettings}
pageInfo={pageInfo}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
/>
</>
)}
<CardSpacer />
<Metadata data={data} onChange={changeMetadata} />
</div>
<div>
<AttributeOrganization
canChangeType={attribute === null}
data={data}
disabled={disabled}
onChange={change}
/>
<CardSpacer />
<AttributeProperties
data={data}
errors={apiErrors}
disabled={disabled}
onChange={change}
/>
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}
onDelete={attribute === null ? undefined : onDelete}
/>
</Container>
<>
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.attributes)}
</Backlink>
<PageHeader
title={
attribute === null
? intl.formatMessage({
defaultMessage: "Create New Attribute",
description: "page title"
})
: maybe(() => attribute.name)
}
/>
<Grid>
<div>
<AttributeDetails
canChangeType={attribute === null}
data={data}
disabled={disabled}
apiErrors={apiErrors}
onChange={change}
set={set}
errors={errors}
setError={setError}
clearErrors={clearErrors}
/>
{ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(
data.inputType
) && (
<>
<CardSpacer />
<AttributeValues
inputType={data.inputType}
disabled={disabled}
values={mapEdgesToItems(values)}
onValueAdd={onValueAdd}
onValueDelete={onValueDelete}
onValueReorder={onValueReorder}
onValueUpdate={onValueUpdate}
settings={settings}
onUpdateListSettings={onUpdateListSettings}
pageInfo={pageInfo}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
/>
</>
)}
<CardSpacer />
<Metadata data={data} onChange={changeMetadata} />
</div>
<div>
<AttributeOrganization
canChangeType={attribute === null}
data={data}
disabled={disabled}
onChange={change}
/>
<CardSpacer />
<AttributeProperties
data={data}
errors={apiErrors}
disabled={disabled}
onChange={change}
/>
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}
onDelete={attribute === null ? undefined : onDelete}
/>
</Container>
{children(data)}
</>
);
}}
</Form>

View file

@ -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<T> = Pick<
UseFormResult<T>,
"setError" | "set" | "errors" | "clearErrors" | "data"
>;
type SwatchType = "picker" | "image";
const AttributeSwatchField: React.FC<AttributeSwatchFieldProps<
AttributeValueEditDialogFormData
>> = ({ 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<SwatchType>(
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 (
<>
<VerticalSpacer spacing={2} />
<RadioGroupField
choices={[
{
label: formatMessage(swatchFieldMessages.picker),
value: "picker"
},
{
label: formatMessage(swatchFieldMessages.image),
value: "image"
}
]}
variant="inline"
label={<FormattedMessage {...inputTypeMessages.swatch} />}
name="swatch"
value={type}
onChange={event => setType(event.target.value)}
data-test="swatch-radio"
/>
{type === "image" ? (
<>
<FileUploadField
disabled={processing}
loading={processing}
file={{ label: null, value: null, file: null }}
onFileUpload={handleFileUpload}
onFileDelete={handleFileDelete}
inputProps={{
accept: "image/*"
}}
/>
{data.fileUrl && (
<div
className={classes.filePreview}
style={{ backgroundImage: `url(${data.fileUrl})` }}
/>
)}
</>
) : (
<ColorPicker {...props} onColorChange={handleColorChange} />
)}
</>
);
};
AttributeSwatchField.displayName = "AttributeSwatchField";
export default AttributeSwatchField;

View file

@ -0,0 +1,2 @@
export { default } from "./AttributeSwatchField";
export * from "./AttributeSwatchField";

View file

@ -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"
}
});

View file

@ -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" }
);

View file

@ -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<AttributeValueEditDialogProps> = ({
@ -39,14 +40,24 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
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 (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
@ -64,7 +75,7 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
)}
</DialogTitle>
<Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, submit }) => (
{({ errors, set, change, clearErrors, setError, data, submit }) => (
<>
<DialogContent>
<TextField
@ -85,6 +96,15 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
value={data.name}
onChange={change}
/>
{isSwatch && (
<AttributeSwatchField
data={data}
errors={errors}
clearErrors={clearErrors}
setError={setError}
set={set}
/>
)}
</DialogContent>
<DialogActions>
<Button onClick={onClose}>

View file

@ -18,8 +18,9 @@ import {
import TablePagination from "@saleor/components/TablePagination";
import { AttributeValueListFragment_edges_node } from "@saleor/fragments/types/AttributeValueListFragment";
import { makeStyles } from "@saleor/macaw-ui";
import { maybe, renderCollection, stopPropagation } from "@saleor/misc";
import { renderCollection, stopPropagation } from "@saleor/misc";
import { ListProps, ReorderAction } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
@ -31,10 +32,14 @@ export interface AttributeValuesProps
onValueDelete: (id: string) => void;
onValueReorder: ReorderAction;
onValueUpdate: (id: string) => void;
inputType: AttributeInputTypeEnum;
}
const useStyles = makeStyles(
theme => ({
columnSwatch: {
width: 100
},
columnAdmin: {
width: 300
},
@ -55,13 +60,18 @@ const useStyles = makeStyles(
},
link: {
cursor: "pointer"
},
swatch: {
width: 32,
height: 32,
borderRadius: 4,
backgroundSize: "cover",
backgroundPosition: "center"
}
}),
{ name: "AttributeValues" }
);
const numberOfColumns = 4;
const AttributeValues: React.FC<AttributeValuesProps> = ({
disabled,
onValueAdd,
@ -73,11 +83,15 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
onUpdateListSettings,
pageInfo,
onNextPage,
onPreviousPage
onPreviousPage,
inputType
}) => {
const classes = useStyles({});
const intl = useIntl();
const isSwatch = inputType === AttributeInputTypeEnum.SWATCH;
const numberOfColumns = isSwatch ? 5 : 4;
return (
<Card>
<CardTitle
@ -103,6 +117,14 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
<TableHead>
<TableRow>
<TableCell className={classes.columnDrag} />
{isSwatch && (
<TableCell className={classes.columnSwatch}>
<FormattedMessage
defaultMessage="Swatch"
description="attribute values list: slug column header"
/>
</TableCell>
)}
<TableCell className={classes.columnAdmin}>
<FormattedMessage
defaultMessage="Admin"
@ -141,14 +163,26 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
className={!!value ? classes.link : undefined}
hover={!!value}
onClick={!!value ? () => onValueUpdate(value.id) : undefined}
key={maybe(() => value.id)}
key={value?.id}
index={valueIndex || 0}
>
{isSwatch && (
<TableCell className={classes.columnSwatch}>
<div
className={classes.swatch}
style={
value?.file
? { backgroundImage: `url(${value.file.url})` }
: { backgroundColor: value.value }
}
/>
</TableCell>
)}
<TableCell className={classes.columnAdmin}>
{maybe(() => value.slug) ? value.slug : <Skeleton />}
{value?.slug ?? <Skeleton />}
</TableCell>
<TableCell className={classes.columnStore}>
{maybe(() => value.name) ? value.name : <Skeleton />}
{value?.name ?? <Skeleton />}
</TableCell>
<TableCell className={classes.iconCell}>
<IconButton
@ -162,7 +196,7 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
),
() => (
<TableRow>
<TableCell colSpan={2}>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage
defaultMessage="No values found"
description="No attribute values found"

View file

@ -52,7 +52,8 @@ export const attribute: AttributeDetails_attribute = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -68,7 +69,8 @@ export const attribute: AttributeDetails_attribute = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]

View file

@ -127,7 +127,7 @@ export const attributeValueUpdateMutation = gql`
${attributeErrorFragment}
mutation AttributeValueUpdate(
$id: ID!
$input: AttributeValueCreateInput!
$input: AttributeValueUpdateInput!
$firstValues: Int
$afterValues: String
$lastValues: Int

View file

@ -46,6 +46,7 @@ export interface AttributeDetails_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface AttributeDetails_attribute_choices_edges {

View file

@ -34,6 +34,7 @@ export interface AttributeValueCreate_attributeValueCreate_attribute_choices_edg
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface AttributeValueCreate_attributeValueCreate_attribute_choices_edges {

View file

@ -34,6 +34,7 @@ export interface AttributeValueDelete_attributeValueDelete_attribute_choices_edg
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface AttributeValueDelete_attributeValueDelete_attribute_choices_edges {

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AttributeValueCreateInput, AttributeErrorCode } from "./../../types/globalTypes";
import { AttributeValueUpdateInput, AttributeErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AttributeValueUpdate
@ -34,6 +34,7 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute_choices_edg
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface AttributeValueUpdate_attributeValueUpdate_attribute_choices_edges {
@ -72,7 +73,7 @@ export interface AttributeValueUpdate {
export interface AttributeValueUpdateVariables {
id: string;
input: AttributeValueCreateInput;
input: AttributeValueUpdateInput;
firstValues?: number | null;
afterValues?: string | null;
lastValues?: number | null;

View file

@ -4,6 +4,7 @@ import {
} from "@saleor/components/Attributes";
import { FileUpload } from "@saleor/files/types/FileUpload";
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment";
import { SelectedVariantAttributeFragment } from "@saleor/fragments/types/SelectedVariantAttributeFragment";
import { UploadErrorFragment } from "@saleor/fragments/types/UploadErrorFragment";
import { FormsetData } from "@saleor/hooks/useFormset";
@ -24,12 +25,12 @@ import {
import { MutationFetchResult } from "react-apollo";
import { AttributePageFormData } from "../components/AttributePage";
import { AttributeValueEditDialogFormData } from "../components/AttributeValueEditDialog";
import { AttributeValueDelete } from "../types/AttributeValueDelete";
export const ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES = [
AttributeInputTypeEnum.DROPDOWN,
AttributeInputTypeEnum.MULTISELECT
AttributeInputTypeEnum.MULTISELECT,
AttributeInputTypeEnum.SWATCH
];
export const ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION = [
@ -38,7 +39,8 @@ export const ATTRIBUTE_TYPES_WITH_CONFIGURABLE_FACED_NAVIGATION = [
AttributeInputTypeEnum.BOOLEAN,
AttributeInputTypeEnum.DATE,
AttributeInputTypeEnum.DATE_TIME,
AttributeInputTypeEnum.NUMERIC
AttributeInputTypeEnum.NUMERIC,
AttributeInputTypeEnum.SWATCH
];
export interface AttributeReference {
@ -46,6 +48,24 @@ export interface AttributeReference {
value: string;
}
export interface AttributeValueEditDialogFormData {
name: string;
value?: string;
fileUrl?: string;
contentType?: string;
}
export function attributeValueFragmentToFormData(
data: AttributeValueFragment | null
): AttributeValueEditDialogFormData {
return {
name: data?.name,
value: data?.value,
contentType: data?.file?.contentType,
fileUrl: data?.file?.url
};
}
function getSimpleAttributeData(
data: AttributePageFormData,
values: AttributeValueEditDialogFormData[]
@ -61,6 +81,31 @@ function getSimpleAttributeData(
};
}
function getAttributeValueTypeFields({
fileUrl,
value,
name,
contentType
}: AttributeValueEditDialogFormData) {
return {
name,
...(fileUrl ? { fileUrl, contentType } : { value })
};
}
function getSwatchAttributeData(
data: AttributePageFormData,
values: AttributeValueEditDialogFormData[]
) {
return {
...data,
metadata: undefined,
privateMetadata: undefined,
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 10),
values: values.map(getAttributeValueTypeFields)
};
}
function getFileOrReferenceAttributeData(
data: AttributePageFormData,
values: AttributeValueEditDialogFormData[]
@ -77,7 +122,9 @@ export function getAttributeData(
data: AttributePageFormData,
values: AttributeValueEditDialogFormData[]
) {
if (ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(data.inputType)) {
if (data.inputType === AttributeInputTypeEnum.SWATCH) {
return getSwatchAttributeData(data, values);
} else if (ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(data.inputType)) {
return getSimpleAttributeData(data, values);
} else {
return getFileOrReferenceAttributeData(data, values);

View file

@ -19,7 +19,8 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -42,7 +43,8 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -54,7 +56,8 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -66,7 +69,8 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -93,7 +97,8 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},

View file

@ -1,4 +1,3 @@
import { getAttributeData } from "@saleor/attributes/utils/data";
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
import useListSettings from "@saleor/hooks/useListSettings";
import useLocalPageInfo, { getMaxPage } from "@saleor/hooks/useLocalPageInfo";
@ -28,9 +27,7 @@ import AttributePage, {
AttributePageFormData
} from "../../components/AttributePage";
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
import AttributeValueEditDialog, {
AttributeValueEditDialogFormData
} from "../../components/AttributeValueEditDialog";
import AttributeValueEditDialog from "../../components/AttributeValueEditDialog";
import { useAttributeCreateMutation } from "../../mutations";
import {
attributeAddUrl,
@ -39,6 +36,10 @@ import {
attributeListUrl,
attributeUrl
} from "../../urls";
import {
AttributeValueEditDialogFormData,
getAttributeData
} from "../../utils/data";
interface AttributeDetailsProps {
params: AttributeAddUrlQueryParams;
@ -113,6 +114,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
setValues(newValues);
closeModal();
};
const handleValueUpdate = (input: AttributeValueEditDialogFormData) => {
if (isSelected(input, values, areValuesEqual)) {
setValueErrors([attributeValueAlreadyExistsError]);
@ -121,21 +123,26 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
closeModal();
}
};
const handleValueCreate = (input: AttributeValueEditDialogFormData) => {
if (isSelected(input, values, areValuesEqual)) {
setValueErrors([attributeValueAlreadyExistsError]);
} else {
const newValues = add(input, values);
setValues(newValues);
const addedToNotVisibleLastPage =
newValues.length - pageInfo.startCursor > settings.rowNumber;
if (addedToNotVisibleLastPage) {
const maxPage = getMaxPage(newValues.length, settings.rowNumber);
loadPage(maxPage);
}
closeModal();
}
};
const handleValueReorder = ({ newIndex, oldIndex }: ReorderEvent) =>
setValues(
move(
@ -164,92 +171,103 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
);
return (
<>
<AttributePage
attribute={null}
disabled={attributeCreateOpts.loading}
errors={attributeCreateOpts.data?.attributeCreate.errors || []}
onBack={() => navigate(attributeListUrl())}
onDelete={undefined}
onSubmit={handleSubmit}
onValueAdd={() => openModal("add-value")}
onValueDelete={id =>
openModal("remove-value", {
id
})
}
onValueReorder={handleValueReorder}
onValueUpdate={id =>
openModal("edit-value", {
id
})
}
saveButtonBarState={attributeCreateOpts.status}
values={{
__typename: "AttributeValueCountableConnection" as "AttributeValueCountableConnection",
pageInfo: {
__typename: "PageInfo" as "PageInfo",
endCursor: "",
hasNextPage: false,
hasPreviousPage: false,
startCursor: ""
},
edges: pageValues.map((value, valueIndex) => ({
__typename: "AttributeValueCountableEdge" as "AttributeValueCountableEdge",
cursor: "1",
node: {
__typename: "AttributeValue" as "AttributeValue",
file: null,
id: valueIndex.toString(),
reference: null,
slug: slugify(value.name).toLowerCase(),
sortOrder: valueIndex,
value: null,
richText: null,
boolean: null,
date: null,
dateTime: null,
...value
}
}))
}}
settings={settings}
onUpdateListSettings={updateListSettings}
pageInfo={pageInfo}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
/>
<AttributeValueEditDialog
attributeValue={null}
confirmButtonState="default"
disabled={false}
errors={valueErrors}
open={params.action === "add-value"}
onClose={closeModal}
onSubmit={handleValueCreate}
/>
{values.length > 0 && (
<AttributePage
attribute={null}
disabled={attributeCreateOpts.loading}
errors={attributeCreateOpts.data?.attributeCreate.errors || []}
onBack={() => navigate(attributeListUrl())}
onDelete={undefined}
onSubmit={handleSubmit}
onValueAdd={() => openModal("add-value")}
onValueDelete={id =>
openModal("remove-value", {
id
})
}
onValueReorder={handleValueReorder}
onValueUpdate={id =>
openModal("edit-value", {
id
})
}
saveButtonBarState={attributeCreateOpts.status}
values={{
__typename: "AttributeValueCountableConnection" as "AttributeValueCountableConnection",
pageInfo: {
__typename: "PageInfo" as "PageInfo",
endCursor: "",
hasNextPage: false,
hasPreviousPage: false,
startCursor: ""
},
edges: pageValues.map((value, valueIndex) => ({
__typename: "AttributeValueCountableEdge" as "AttributeValueCountableEdge",
cursor: "1",
node: {
__typename: "AttributeValue" as "AttributeValue",
file: value?.fileUrl
? {
url: value.fileUrl,
contentType: value.contentType,
__typename: "File"
}
: null,
id: valueIndex.toString(),
reference: null,
slug: slugify(value.name).toLowerCase(),
sortOrder: valueIndex,
value: null,
richText: null,
boolean: null,
date: null,
dateTime: null,
...value
}
}))
}}
settings={settings}
onUpdateListSettings={updateListSettings}
pageInfo={pageInfo}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
>
{data => (
<>
<AttributeValueDeleteDialog
attributeName={undefined}
open={params.action === "remove-value"}
name={getStringOrPlaceholder(values[id]?.name)}
confirmButtonState="default"
onClose={closeModal}
onConfirm={handleValueDelete}
/>
<AttributeValueEditDialog
attributeValue={values[id]}
attributeValue={null}
confirmButtonState="default"
disabled={false}
errors={valueErrors}
open={params.action === "edit-value"}
open={params.action === "add-value"}
onClose={closeModal}
onSubmit={handleValueUpdate}
onSubmit={handleValueCreate}
inputType={data.inputType}
/>
{values.length > 0 && (
<>
<AttributeValueDeleteDialog
attributeName={undefined}
open={params.action === "remove-value"}
name={getStringOrPlaceholder(values[id]?.name)}
confirmButtonState="default"
onClose={closeModal}
onConfirm={handleValueDelete}
/>
<AttributeValueEditDialog
inputType={data.inputType}
attributeValue={values[id]}
confirmButtonState="default"
disabled={false}
errors={valueErrors}
open={params.action === "edit-value"}
onClose={closeModal}
onSubmit={handleValueUpdate}
/>
</>
)}
</>
)}
</>
</AttributePage>
);
};
AttributeDetails.displayName = "AttributeDetails";

View file

@ -1,3 +1,4 @@
import { attributeValueFragmentToFormData } from "@saleor/attributes/utils/data";
import useListSettings from "@saleor/hooks/useListSettings";
import useLocalPaginator, {
useLocalPaginationState
@ -5,7 +6,6 @@ import useLocalPaginator, {
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { ListViews, ReorderEvent } from "@saleor/types";
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
@ -85,7 +85,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const [attributeDelete, attributeDeleteOpts] = useAttributeDeleteMutation({
onCompleted: data => {
if (data.attributeDelete.errors.length === 0) {
if (data?.attributeDelete.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage({
@ -102,7 +102,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeValueDeleteOpts
] = useAttributeValueDeleteMutation({
onCompleted: data => {
if (data.attributeValueDelete.errors.length === 0) {
if (data?.attributeValueDelete.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage({
@ -120,7 +120,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeValueUpdateOpts
] = useAttributeValueUpdateMutation({
onCompleted: data => {
if (data.attributeValueUpdate.errors.length === 0) {
if (data?.attributeValueUpdate.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges)
@ -132,7 +132,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const [attributeUpdate, attributeUpdateOpts] = useAttributeUpdateMutation({
onCompleted: data => {
if (data.attributeUpdate.errors.length === 0) {
if (data?.attributeUpdate.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges)
@ -146,7 +146,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeValueCreateOpts
] = useAttributeValueCreateMutation({
onCompleted: data => {
if (data.attributeValueCreate.errors.length === 0) {
if (data?.attributeValueCreate.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage({
@ -161,11 +161,11 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const [attributeValueReorder] = useAttributeValueReorderMutation({
onCompleted: data => {
if (data.attributeReorderValues.errors.length !== 0) {
if (data?.attributeReorderValues.errors.length !== 0) {
notify({
status: "error",
text: getAttributeErrorMessage(
data.attributeReorderValues.errors[0],
data?.attributeReorderValues.errors[0],
intl
)
});
@ -179,15 +179,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeReorderValues: {
__typename: "AttributeReorderValues",
attribute: {
...data.attribute,
...data?.attribute,
choices: {
__typename: "AttributeValueCountableConnection",
pageInfo: {
...data.attribute.choices.pageInfo
...data?.attribute.choices.pageInfo
},
edges: move(
data.attribute.choices.edges[oldIndex],
data.attribute.choices.edges,
data?.attribute.choices.edges[oldIndex],
data?.attribute.choices.edges,
(a, b) => a.node.id === b.node.id,
newIndex
)
@ -199,7 +199,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
variables: {
id,
move: {
id: data.attribute.choices.edges[oldIndex].node.id,
id: data?.attribute.choices.edges[oldIndex].node.id,
sortOrder: newIndex - oldIndex
},
firstValues: valuesPaginationState.first,
@ -216,7 +216,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
inputType: undefined,
metadata: undefined,
privateMetadata: undefined,
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 0)
storefrontSearchPosition: parseInt(data?.storefrontSearchPosition, 0)
};
const result = await attributeUpdate({
@ -226,7 +226,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
}
});
return result.data.attributeUpdate.errors;
return result.data?.attributeUpdate.errors;
};
const handleSubmit = createMetadataUpdateHandler(
@ -237,124 +237,127 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
);
return (
<>
<AttributePage
attribute={maybe(() => data.attribute)}
disabled={loading}
errors={attributeUpdateOpts.data?.attributeUpdate.errors || []}
onBack={() => navigate(attributeListUrl())}
onDelete={() => openModal("remove")}
onSubmit={handleSubmit}
onValueAdd={() => openModal("add-value")}
onValueDelete={id =>
openModal("remove-value", {
id
})
}
onValueReorder={handleValueReorder}
onValueUpdate={id =>
openModal("edit-value", {
id
})
}
saveButtonBarState={attributeUpdateOpts.status}
values={maybe(() => data.attribute.choices)}
settings={settings}
onUpdateListSettings={updateListSettings}
pageInfo={pageInfo}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
/>
<AttributeDeleteDialog
open={params.action === "remove"}
name={maybe(() => data.attribute.name, "...")}
confirmButtonState={attributeDeleteOpts.status}
onClose={closeModal}
onConfirm={() =>
attributeDelete({
variables: {
id
<AttributePage
attribute={data?.attribute}
disabled={loading}
errors={attributeUpdateOpts.data?.attributeUpdate.errors || []}
onBack={() => navigate(attributeListUrl())}
onDelete={() => openModal("remove")}
onSubmit={handleSubmit}
onValueAdd={() => openModal("add-value")}
onValueDelete={id =>
openModal("remove-value", {
id
})
}
onValueReorder={handleValueReorder}
onValueUpdate={id =>
openModal("edit-value", {
id
})
}
saveButtonBarState={attributeUpdateOpts.status}
values={data?.attribute?.choices}
settings={settings}
onUpdateListSettings={updateListSettings}
pageInfo={pageInfo}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
>
{attributeFormData => (
<>
<AttributeDeleteDialog
open={params.action === "remove"}
name={data?.attribute?.name ?? "..."}
confirmButtonState={attributeDeleteOpts.status}
onClose={closeModal}
onConfirm={() =>
attributeDelete({
variables: {
id
}
})
}
})
}
/>
<AttributeValueDeleteDialog
attributeName={maybe(() => data.attribute.name, "...")}
open={params.action === "remove-value"}
name={maybe(
() =>
data.attribute.choices.edges.find(
value => params.id === value.node.id
).node.name,
"..."
)}
useName={true}
confirmButtonState={attributeValueDeleteOpts.status}
onClose={closeModal}
onConfirm={() =>
attributeValueDelete({
variables: {
id: params.id,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
}
})
}
/>
<AttributeValueEditDialog
attributeValue={null}
confirmButtonState={attributeValueCreateOpts.status}
disabled={loading}
errors={
attributeValueCreateOpts.data?.attributeValueCreate.errors || []
}
open={params.action === "add-value"}
onClose={closeModal}
onSubmit={input =>
attributeValueCreate({
variables: {
id,
input,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
}
})
}
/>
<AttributeValueEditDialog
attributeValue={maybe(
() =>
data.attribute.choices.edges.find(
value => params.id === value.node.id
).node
)}
confirmButtonState={attributeValueUpdateOpts.status}
disabled={loading}
errors={
attributeValueUpdateOpts.data?.attributeValueUpdate.errors || []
}
open={params.action === "edit-value"}
onClose={closeModal}
onSubmit={input =>
attributeValueUpdate({
variables: {
id: data.attribute.choices.edges.find(
/>
<AttributeValueDeleteDialog
attributeName={data?.attribute?.name ?? "..."}
open={params.action === "remove-value"}
name={
(data?.attribute?.choices?.edges?.find(
value => params.id === value.node.id
).node.id,
input,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
)?.node.name,
"...")
}
})
}
/>
</>
useName={true}
confirmButtonState={attributeValueDeleteOpts.status}
onClose={closeModal}
onConfirm={() =>
attributeValueDelete({
variables: {
id: params.id,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
}
})
}
/>
<AttributeValueEditDialog
inputType={attributeFormData.inputType}
attributeValue={null}
confirmButtonState={attributeValueCreateOpts.status}
disabled={loading}
errors={
attributeValueCreateOpts.data?.attributeValueCreate.errors || []
}
open={params.action === "add-value"}
onClose={closeModal}
onSubmit={input =>
attributeValueCreate({
variables: {
id,
input,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
}
})
}
/>
<AttributeValueEditDialog
inputType={attributeFormData.inputType}
attributeValue={attributeValueFragmentToFormData(
data?.attribute?.choices?.edges?.find(
value => params.id === value.node.id
)?.node
)}
confirmButtonState={attributeValueUpdateOpts.status}
disabled={loading}
errors={
attributeValueUpdateOpts.data?.attributeValueUpdate.errors || []
}
open={params.action === "edit-value"}
onClose={closeModal}
onSubmit={input =>
attributeValueUpdate({
variables: {
id: data?.attribute.choices.edges.find(
value => params.id === value.node.id
).node.id,
input,
firstValues: valuesPaginationState.first,
lastValues: valuesPaginationState.last,
afterValues: valuesPaginationState.after,
beforeValues: valuesPaginationState.before
}
})
}
/>
</>
)}
</AttributePage>
);
};
AttributeDetails.displayName = "AttributeDetails";

View file

@ -1,9 +1,9 @@
import { InputAdornment, TextField } from "@material-ui/core";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { getMeasurementUnitMessage } from "@saleor/attributes/components/AttributeDetails/utils";
import { AttributeInput } from "@saleor/components/Attributes/Attributes";
import BasicAttributeRow from "@saleor/components/Attributes/BasicAttributeRow";
import ExtendedAttributeRow from "@saleor/components/Attributes/ExtendedAttributeRow";
import { attributeRowMessages } from "@saleor/components/Attributes/messages";
import { SwatchRow } from "@saleor/components/Attributes/SwatchRow";
import {
getErrorMessage,
getFileChoice,
@ -21,60 +21,13 @@ import MultiAutocompleteSelectField from "@saleor/components/MultiAutocompleteSe
import RichTextEditor from "@saleor/components/RichTextEditor";
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
import SortableChipsField from "@saleor/components/SortableChipsField";
import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment";
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { FormsetChange } from "@saleor/hooks/useFormset";
import { commonMessages } from "@saleor/intl";
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
import { useIntl } from "react-intl";
const messages = defineMessages({
multipleValueLabel: {
defaultMessage: "Values",
description: "attribute values"
},
valueLabel: {
defaultMessage: "Value",
description: "attribute value"
}
});
const useStyles = makeStyles(
() => ({
fileField: {
float: "right"
},
pullRight: {
display: "flex",
justifyContent: "flex-end"
}
}),
{ name: "AttributeRow" }
);
export interface AttributeRowHandlers {
onChange: FormsetChange<string>;
onFileChange: FormsetChange<File>;
onMultiChange: FormsetChange<string>;
onReferencesAddClick: (attribute: AttributeInput) => void;
onReferencesRemove: FormsetChange<string[]>;
onReferencesReorder: FormsetChange<ReorderEvent>;
fetchAttributeValues: (query: string, attributeId: string) => void;
fetchMoreAttributeValues: FetchMoreProps;
}
interface AttributeRowProps extends AttributeRowHandlers {
attribute: AttributeInput;
attributeValues: AttributeValueFragment[];
disabled: boolean;
error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment;
loading: boolean;
entityId: string;
onAttributeSelectBlur?: () => void;
}
import { useStyles } from "./styles";
import { AttributeRowProps } from "./types";
const AttributeRow: React.FC<AttributeRowProps> = ({
attribute,
@ -94,17 +47,14 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
onAttributeSelectBlur
}) => {
const intl = useIntl();
const classes = useStyles({});
const classes = useStyles();
switch (attribute.data.inputType) {
case AttributeInputTypeEnum.REFERENCE:
return (
<ExtendedAttributeRow
label={attribute.label}
selectLabel={intl.formatMessage({
defaultMessage: "Assign references",
description: "button label"
})}
selectLabel={intl.formatMessage(attributeRowMessages.reference)}
onSelect={() => onReferencesAddClick(attribute)}
disabled={disabled}
>
@ -152,7 +102,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
error={!!error}
helperText={getErrorMessage(error, intl)}
name={`attribute:${attribute.label}`}
label={intl.formatMessage(messages.valueLabel)}
label={intl.formatMessage(attributeRowMessages.valueLabel)}
value={attribute.value[0]}
onChange={event => onChange(attribute.id, event.target.value)}
allowCustomValues={true}
@ -163,6 +113,18 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
/>
</BasicAttributeRow>
);
case AttributeInputTypeEnum.SWATCH:
return (
<SwatchRow
attribute={attribute}
attributeValues={attributeValues}
onChange={onChange}
disabled={disabled}
error={error}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
/>
);
case AttributeInputTypeEnum.RICH_TEXT:
return (
<BasicAttributeRow label={attribute.label}>
@ -171,7 +133,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
name={`attribute:${attribute.label}`}
disabled={disabled}
error={!!error}
label={intl.formatMessage(messages.valueLabel)}
label={intl.formatMessage(attributeRowMessages.valueLabel)}
helperText={getErrorMessage(error, intl)}
onChange={data => onChange(attribute.id, JSON.stringify(data))}
data={getRichTextData(attribute)}
@ -186,7 +148,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
disabled={disabled}
error={!!error}
helperText={getErrorMessage(error, intl)}
label={intl.formatMessage(messages.valueLabel)}
label={intl.formatMessage(attributeRowMessages.valueLabel)}
name={`attribute:${attribute.label}`}
onChange={event => onChange(attribute.id, event.target.value)}
type="number"
@ -264,7 +226,7 @@ const AttributeRow: React.FC<AttributeRowProps> = ({
disabled={disabled}
error={!!error}
helperText={getErrorMessage(error, intl)}
label={intl.formatMessage(messages.multipleValueLabel)}
label={intl.formatMessage(attributeRowMessages.multipleValueLabel)}
name={`attribute:${attribute.label}`}
value={attribute.value}
onChange={event => onMultiChange(attribute.id, event.target.value)}

View file

@ -18,8 +18,8 @@ import classNames from "classnames";
import React from "react";
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
import AttributeRow, { AttributeRowHandlers } from "./AttributeRow";
import { VariantAttributeScope } from "./types";
import AttributeRow from "./AttributeRow";
import { AttributeRowHandlers, VariantAttributeScope } from "./types";
export interface AttributeInputData {
inputType: AttributeInputTypeEnum;

View file

@ -0,0 +1,90 @@
import { InputAdornment } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import BasicAttributeRow from "@saleor/components/Attributes/BasicAttributeRow";
import {
getErrorMessage,
getSingleDisplayValue
} from "@saleor/components/Attributes/utils";
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
import { getBySlug } from "@saleor/products/components/ProductVariantCreatorPage/utils";
import React from "react";
import { useIntl } from "react-intl";
import { useStyles } from "./styles";
import { AttributeRowProps } from "./types";
type SwatchRowProps = Pick<
AttributeRowProps,
| "attribute"
| "attributeValues"
| "disabled"
| "error"
| "onChange"
| "fetchAttributeValues"
| "fetchMoreAttributeValues"
>;
export const SwatchRow: React.FC<SwatchRowProps> = ({
attributeValues,
fetchAttributeValues,
fetchMoreAttributeValues,
attribute,
disabled,
error,
onChange
}) => {
const classes = useStyles();
const intl = useIntl();
const value = attribute.data.values.find(getBySlug(attribute.value[0]));
return (
<BasicAttributeRow label={attribute.label}>
<SingleAutocompleteSelectField
fetchOnFocus
allowCustomValues={false}
choices={attributeValues.map(({ file, value, slug, name }) => ({
label: (
<>
<div
className={classes.swatchPreview}
style={
file
? { backgroundImage: `url(${file.url})` }
: { backgroundColor: value }
}
/>
<HorizontalSpacer />
{name}
</>
),
value: slug
}))}
disabled={disabled}
displayValue={getSingleDisplayValue(attribute, attributeValues)}
emptyOption={!attribute.data.isRequired}
error={!!error}
helperText={getErrorMessage(error, intl)}
name={`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: (
<InputAdornment position="start">
<div
className={classes.swatchPreview}
style={
value?.file
? { backgroundImage: `url(${value.file.url})` }
: { backgroundColor: value?.value }
}
/>
</InputAdornment>
)
}}
{...fetchMoreAttributeValues}
/>
</BasicAttributeRow>
);
};

View file

@ -21,7 +21,8 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -33,7 +34,8 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -57,7 +59,8 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -69,7 +72,8 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -81,7 +85,8 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -109,7 +114,8 @@ const FILE_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -148,7 +154,8 @@ const REFERENCE_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -160,7 +167,8 @@ const REFERENCE_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
},
{
__typename: "AttributeValue",
@ -172,7 +180,8 @@ const REFERENCE_ATTRIBUTE: AttributeInput = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -200,7 +209,8 @@ const RICH_TEXT_ATTRIBUTE: AttributeInput = {
}),
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
],
selectedValues: []
@ -226,6 +236,7 @@ const NUMERIC_ATTRIBUTE: AttributeInput = {
boolean: null,
date: null,
dateTime: null,
value: null,
slug: "319_35"
}
]
@ -250,7 +261,8 @@ const BOOLEAN_ATTRIBUTE: AttributeInput = {
boolean: true,
slug: "319_True",
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -274,7 +286,8 @@ const DATE_ATTRIBUTE: AttributeInput = {
boolean: true,
slug: "319_True",
date: "2021-06-03",
dateTime: "2021-06-03 00:15:00+00:00"
dateTime: "2021-06-03 00:15:00+00:00",
value: null
}
]
},
@ -298,7 +311,8 @@ const DATE_TIME_ATTRIBUTE: AttributeInput = {
boolean: true,
slug: "319_True",
date: "2021-06-03",
dateTime: "2021-06-03 00:15:00+00:00"
dateTime: "2021-06-03 00:15:00+00:00",
value: null
}
]
},
@ -306,6 +320,32 @@ const DATE_TIME_ATTRIBUTE: AttributeInput = {
label: "Date Time Attribute",
value: []
};
const SWATCH_ATTRIBUTE: AttributeInput = {
data: {
inputType: AttributeInputTypeEnum.SWATCH,
isRequired: true,
values: [
{
__typename: "AttributeValue",
file: null,
id: "sdfgsdgsdfg",
name: "Red",
reference: null,
richText: null,
boolean: true,
slug: "315_11",
date: null,
dateTime: null,
value: "#FF0000"
}
]
},
id: "QXR0cmlidXasdfasdfasdf1",
label: "Swatch Attribute",
value: []
};
export const ATTRIBUTES: AttributeInput[] = [
DROPDOWN_ATTRIBUTE,
MULTISELECT_ATTRIBUTE,
@ -315,7 +355,8 @@ export const ATTRIBUTES: AttributeInput[] = [
NUMERIC_ATTRIBUTE,
BOOLEAN_ATTRIBUTE,
DATE_ATTRIBUTE,
DATE_TIME_ATTRIBUTE
DATE_TIME_ATTRIBUTE,
SWATCH_ATTRIBUTE
];
export const ATTRIBUTES_SELECTED: AttributeInput[] = [
@ -365,5 +406,9 @@ export const ATTRIBUTES_SELECTED: AttributeInput[] = [
{
...DATE_TIME_ATTRIBUTE,
value: [DATE_TIME_ATTRIBUTE.data.values[0].dateTime]
},
{
...SWATCH_ATTRIBUTE,
value: [SWATCH_ATTRIBUTE.data.values[0].slug]
}
];

View file

@ -0,0 +1,16 @@
import { defineMessages } from "react-intl";
export const attributeRowMessages = defineMessages({
multipleValueLabel: {
defaultMessage: "Values",
description: "attribute values"
},
valueLabel: {
defaultMessage: "Value",
description: "attribute value"
},
reference: {
defaultMessage: "Assign references",
description: "button label"
}
});

View file

@ -0,0 +1,25 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
() => ({
fileField: {
float: "right"
},
pullRight: {
display: "flex",
justifyContent: "flex-end"
},
swatchInput: {
paddingTop: 16.5,
paddingBottom: 16.5
},
swatchPreview: {
width: 32,
height: 32,
borderRadius: 4,
backgroundSize: "cover",
backgroundPosition: "center"
}
}),
{ name: "AttributeRow" }
);

View file

@ -1,5 +1,34 @@
import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment";
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { FormsetChange } from "@saleor/hooks/useFormset";
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
import { AttributeInput } from "./Attributes";
export enum VariantAttributeScope {
ALL = "ALL",
VARIANT_SELECTION = "VARIANT_SELECTION",
NOT_VARIANT_SELECTION = "NOT_VARIANT_SELECTION"
}
export interface AttributeRowHandlers {
onChange: FormsetChange<string>;
onFileChange: FormsetChange<File>;
onMultiChange: FormsetChange<string>;
onReferencesAddClick: (attribute: AttributeInput) => void;
onReferencesRemove: FormsetChange<string[]>;
onReferencesReorder: FormsetChange<ReorderEvent>;
fetchAttributeValues: (query: string, attributeId: string) => void;
fetchMoreAttributeValues: FetchMoreProps;
}
export interface AttributeRowProps extends AttributeRowHandlers {
attribute: AttributeInput;
attributeValues: AttributeValueFragment[];
disabled: boolean;
error: ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment;
loading: boolean;
entityId: string;
onAttributeSelectBlur?: () => void;
}

View file

@ -0,0 +1,19 @@
import CentralPlacementDecorator from "@saleor/storybook/CentralPlacementDecorator";
import Decorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import { ColorPicker, ColorPickerProps } from "./ColorPicker";
const props: ColorPickerProps = {
data: {},
setError: () => null,
errors: {},
clearErrors: () => null,
onColorChange: () => null
};
storiesOf("Generics / ColorPicker", module)
.addDecorator(Decorator)
.addDecorator(CentralPlacementDecorator)
.add("default", () => <ColorPicker {...props} />);

View file

@ -0,0 +1,153 @@
import { TextField } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import { UseFormResult } from "@saleor/hooks/useForm";
import { makeStyles } from "@saleor/macaw-ui";
import { RequireOnlyOne } from "@saleor/misc";
import commonErrorMessages from "@saleor/utils/errors/common";
import Hue from "@uiw/react-color-hue";
import Saturation from "@uiw/react-color-saturation";
import convert from "color-convert";
import { RGB } from "color-convert/conversions";
import React, { useEffect, useState } from "react";
import { useIntl } from "react-intl";
const useStyles = makeStyles(
theme => ({
picker: {
display: "flex"
},
saturation: {
width: "220px !important",
height: "220px !important"
},
colorInput: {
whiteSpace: "nowrap",
width: "170px",
marginBottom: theme.spacing(1),
"& input": {
textAlign: "right",
padding: "15px"
}
}
}),
{ name: "ColorPicker" }
);
export type ColorPickerProps<T = any> = Pick<
UseFormResult<T>,
"setError" | "errors" | "clearErrors" | "data"
> & { onColorChange: (hex: string) => void };
export const ColorPicker: React.FC<ColorPickerProps> = ({
clearErrors,
setError,
errors,
onColorChange,
data
}) => {
const classes = useStyles();
const intl = useIntl();
const [hex, setHex] = useState<string>(
data.value ? data.value.replace("#", "") : "000000"
);
const [hue, setHue] = useState<number>(convert.hex.hsv(hex)[0]);
const [_, s, v] = convert.hex.hsv(hex);
const [r, g, b] = convert.hex.rgb(hex);
const isValidColor = hex.match(/^(?:[0-9a-fA-F]{3}){1,2}$/);
const handleRGBChange = (
rgbColor: RequireOnlyOne<{ r: string; g: string; b: string }>
) => {
const getValue = (val: string): number => {
if (!val) {
return 0;
}
const parsedVal = parseInt(val, 10);
return parsedVal > 255 ? 255 : parsedVal;
};
setHex(
convert.rgb.hex([
getValue(rgbColor.r),
getValue(rgbColor.g),
getValue(rgbColor.b)
] as RGB)
);
};
const handleHEXChange = (hexColor: string) =>
setHex(hexColor.replace(/ |#/g, ""));
useEffect(() => {
if (isValidColor) {
if ("value" in errors) {
clearErrors("value");
}
onColorChange(`#${hex}`);
} else {
if (!("value" in errors)) {
setError("value", intl.formatMessage(commonErrorMessages.invalid));
}
}
}, [errors, hex]);
return (
<div className={classes.picker}>
<div>
<Saturation
hsva={{ h: hue, s, v, a: 1 }}
onChange={({ h, s, v }) => setHex(convert.hsv.hex([h, s, v]))}
className={classes.saturation}
/>
</div>
<HorizontalSpacer spacing={4} />
<div>
<Hue
hue={hue}
onChange={({ h }) => {
setHue(h);
setHex(convert.hsv.hex([h, s, v]));
}}
direction="vertical"
height="220px"
width="16px"
/>
</div>
<HorizontalSpacer spacing={4} />
<div>
<TextField
className={classes.colorInput}
InputProps={{ startAdornment: "R" }}
value={r}
onChange={event => handleRGBChange({ r: event.target.value })}
/>
<TextField
className={classes.colorInput}
InputProps={{ startAdornment: "G" }}
value={g}
onChange={event => handleRGBChange({ g: event.target.value })}
/>
<TextField
className={classes.colorInput}
InputProps={{ startAdornment: "B" }}
value={b}
onChange={event => handleRGBChange({ b: event.target.value })}
/>
<TextField
error={!isValidColor}
helperText={errors?.value}
className={classes.colorInput}
InputProps={{ startAdornment: "HEX" }}
inputProps={{ pattern: "[A-Za-z0-9]{6}", maxLength: 6 }}
value={`#${hex}`}
onChange={event => handleHEXChange(event.target.value)}
/>
</div>
</div>
);
};

View file

@ -0,0 +1 @@
export * from "./ColorPicker";

View file

@ -36,7 +36,7 @@ export interface SingleAutocompleteSelectFieldProps
name: string;
displayValue: string;
emptyOption?: boolean;
choices: SingleAutocompleteChoiceType[];
choices: Array<SingleAutocompleteChoiceType<string, string | JSX.Element>>;
value: string;
disabled?: boolean;
placeholder?: string;
@ -144,7 +144,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
}
if (isValueInValues && !isValueInLabels) {
reset({ inputValue: choiceFromInputValue.label });
reset({ inputValue: choiceFromInputValue.value });
return;
}

View file

@ -13,7 +13,7 @@ import { makeStyles } from "@saleor/macaw-ui";
import { FetchMoreProps } from "@saleor/types";
import classNames from "classnames";
import { GetItemPropsOptions } from "downshift";
import React from "react";
import React, { ReactElement } from "react";
import SVG from "react-inlinesvg";
import { FormattedMessage } from "react-intl";
@ -25,10 +25,11 @@ const offset = 24;
export type ChoiceValue = string;
export interface SingleAutocompleteChoiceType<
T extends ChoiceValue = ChoiceValue
V extends ChoiceValue = ChoiceValue,
L = string
> {
label: string;
value: T;
label: L;
value: V;
}
export interface SingleAutocompleteActionType {
label: string;
@ -37,7 +38,7 @@ export interface SingleAutocompleteActionType {
export interface SingleAutocompleteSelectFieldContentProps
extends Partial<FetchMoreProps> {
add?: SingleAutocompleteActionType;
choices: SingleAutocompleteChoiceType[];
choices: Array<SingleAutocompleteChoiceType<string, string | JSX.Element>>;
displayCustomValue: boolean;
emptyOption: boolean;
getItemProps: (options: GetItemPropsOptions) => any;
@ -266,11 +267,16 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
displayCustomValue,
!!add
);
const key = React.isValidElement(suggestion.label)
? `${index}${suggestion.value}${
((suggestion as unknown) as ReactElement).props
}`
: JSON.stringify(suggestion);
return (
<MenuItem
className={classes.menuItem}
key={JSON.stringify(suggestion)}
key={key}
selected={selectedItem === suggestion.value}
component="div"
{...getItemProps({

View file

@ -18,6 +18,7 @@ export const attributeValueFragment = gql`
boolean
date
dateTime
value
}
`;

View file

@ -24,4 +24,5 @@ export interface AttributeValueFragment {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}

View file

@ -32,6 +32,7 @@ export interface AttributeValueListFragment_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface AttributeValueListFragment_edges {

View file

@ -34,6 +34,7 @@ export interface PageAttributesFragment_attributes_attribute_choices_edges_node
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageAttributesFragment_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface PageAttributesFragment_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageAttributesFragment_attributes {
@ -110,6 +112,7 @@ export interface PageAttributesFragment_pageType_attributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageAttributesFragment_pageType_attributes_choices_edges {

View file

@ -34,6 +34,7 @@ export interface PageDetailsFragment_attributes_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetailsFragment_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface PageDetailsFragment_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetailsFragment_attributes {
@ -110,6 +112,7 @@ export interface PageDetailsFragment_pageType_attributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetailsFragment_pageType_attributes_choices_edges {

View file

@ -34,6 +34,7 @@ export interface Product_attributes_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface Product_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface Product_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface Product_attributes {
@ -110,6 +112,7 @@ export interface Product_productType_variantAttributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface Product_productType_variantAttributes_choices_edges {

View file

@ -5,9 +5,9 @@
import { AttributeInputTypeEnum, AttributeEntityTypeEnum, MeasurementUnitsEnum, ProductMediaType, WeightUnitsEnum } from "./../../types/globalTypes";
// ====================================================
//===
// GraphQL fragment: ProductVariant
// ====================================================
//===
export interface ProductVariant_metadata {
__typename: "MetadataItem";
@ -46,6 +46,7 @@ export interface ProductVariant_selectionAttributes_attribute_choices_edges_node
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariant_selectionAttributes_attribute_choices_edges {
@ -89,6 +90,7 @@ export interface ProductVariant_selectionAttributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariant_selectionAttributes {
@ -122,6 +124,7 @@ export interface ProductVariant_nonSelectionAttributes_attribute_choices_edges_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariant_nonSelectionAttributes_attribute_choices_edges {
@ -165,6 +168,7 @@ export interface ProductVariant_nonSelectionAttributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariant_nonSelectionAttributes {

View file

@ -34,6 +34,7 @@ export interface ProductVariantAttributesFragment_attributes_attribute_choices_e
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantAttributesFragment_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface ProductVariantAttributesFragment_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantAttributesFragment_attributes {
@ -110,6 +112,7 @@ export interface ProductVariantAttributesFragment_productType_variantAttributes_
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantAttributesFragment_productType_variantAttributes_choices_edges {

View file

@ -34,6 +34,7 @@ export interface SelectedVariantAttributeFragment_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SelectedVariantAttributeFragment_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface SelectedVariantAttributeFragment_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SelectedVariantAttributeFragment {

View file

@ -34,6 +34,7 @@ export interface VariantAttributeFragment_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantAttributeFragment_choices_edges {

View file

@ -72,7 +72,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -88,7 +89,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -104,7 +106,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -122,7 +125,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
],
__typename: "SelectedAttribute"
@ -159,7 +163,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -175,7 +180,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -191,7 +197,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -207,7 +214,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -225,7 +233,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
],
__typename: "SelectedAttribute"
@ -275,7 +284,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -291,7 +301,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -307,7 +318,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -343,7 +355,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -359,7 +372,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -375,7 +389,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -391,7 +406,8 @@ export const page: PageDetails_page = {
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]

View file

@ -34,6 +34,7 @@ export interface PageDetails_page_attributes_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetails_page_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface PageDetails_page_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetails_page_attributes {
@ -110,6 +112,7 @@ export interface PageDetails_page_pageType_attributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageDetails_page_pageType_attributes_choices_edges {

View file

@ -34,6 +34,7 @@ export interface PageType_pageType_attributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageType_pageType_attributes_choices_edges {

View file

@ -41,6 +41,7 @@ export interface PageUpdate_pageUpdate_page_attributes_attribute_choices_edges_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageUpdate_pageUpdate_page_attributes_attribute_choices_edges {
@ -84,6 +85,7 @@ export interface PageUpdate_pageUpdate_page_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageUpdate_pageUpdate_page_attributes {
@ -117,6 +119,7 @@ export interface PageUpdate_pageUpdate_page_pageType_attributes_choices_edges_no
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface PageUpdate_pageUpdate_page_pageType_attributes_choices_edges {

View file

@ -128,7 +128,7 @@ const Option: React.FC<{
className={classes.label}
label={children}
labelPlacement="start"
></FormControlLabel>
/>
);
};

View file

@ -19,6 +19,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -34,6 +35,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -54,6 +56,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -69,6 +72,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -89,6 +93,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -104,6 +109,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -658,6 +664,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -673,6 +680,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -693,6 +701,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -708,6 +717,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -728,6 +738,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -743,6 +754,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -1297,6 +1309,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -1312,6 +1325,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -1332,6 +1346,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -1347,6 +1362,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -1367,6 +1383,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -1382,6 +1399,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -1436,6 +1454,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -1451,6 +1470,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -1471,6 +1491,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -1486,6 +1507,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -1506,6 +1528,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -1521,6 +1544,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -2026,6 +2050,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -2041,6 +2066,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -2061,6 +2087,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -2076,6 +2103,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -2096,6 +2124,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -2111,6 +2140,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -2571,6 +2601,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -2586,6 +2617,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -2606,6 +2638,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -2621,6 +2654,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -2641,6 +2675,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -2656,6 +2691,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -3161,6 +3197,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -3176,6 +3213,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -3196,6 +3234,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -3211,6 +3250,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -3231,6 +3271,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -3246,6 +3287,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],
@ -3668,6 +3710,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-1",
"value": null,
},
},
Object {
@ -3683,6 +3726,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-1-7",
"value": null,
},
},
],
@ -3703,6 +3747,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-2",
"value": null,
},
},
Object {
@ -3718,6 +3763,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-2-4",
"value": null,
},
},
],
@ -3738,6 +3784,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-1",
"value": null,
},
},
Object {
@ -3753,6 +3800,7 @@ Object {
"reference": null,
"richText": null,
"slug": "val-4-5",
"value": null,
},
},
],

View file

@ -32,7 +32,8 @@ export const attributes = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}))
},
@ -52,7 +53,8 @@ export const attributes = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}))
},
@ -72,7 +74,8 @@ export const attributes = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}))
},
@ -92,7 +95,8 @@ export const attributes = [
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}))
}

View file

@ -53,7 +53,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -69,7 +70,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -86,7 +88,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -124,7 +127,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -140,7 +144,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -156,7 +161,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -172,7 +178,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -189,7 +196,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -387,7 +395,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -427,7 +436,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -443,7 +453,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -489,7 +500,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -524,7 +536,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -540,7 +553,8 @@ export const product: (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -1002,7 +1016,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1114,7 +1129,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1226,7 +1242,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1339,7 +1356,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1451,7 +1469,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1563,7 +1582,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1675,7 +1695,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1787,7 +1808,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -1899,7 +1921,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2011,7 +2034,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2123,7 +2147,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2235,7 +2260,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2347,7 +2373,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2459,7 +2486,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2571,7 +2599,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2683,7 +2712,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -2795,7 +2825,8 @@ export const products = (
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -3009,7 +3040,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -3030,7 +3062,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}
@ -3294,7 +3327,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -3310,7 +3344,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -3327,7 +3362,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
},
@ -3365,7 +3401,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -3381,7 +3418,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -3397,7 +3435,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
},
{
@ -3413,7 +3452,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
}
]
@ -3430,7 +3470,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
richText: null,
boolean: null,
date: null,
dateTime: null
dateTime: null,
value: null
}
]
}

View file

@ -34,6 +34,7 @@ export interface CreateMultipleVariantsData_product_attributes_attribute_choices
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface CreateMultipleVariantsData_product_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface CreateMultipleVariantsData_product_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface CreateMultipleVariantsData_product_attributes {
@ -110,6 +112,7 @@ export interface CreateMultipleVariantsData_product_productType_variantAttribute
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface CreateMultipleVariantsData_product_productType_variantAttributes_choices_edges {

View file

@ -34,6 +34,7 @@ export interface ProductDetails_product_attributes_attribute_choices_edges_node
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductDetails_product_attributes_attribute_choices_edges {
@ -77,6 +78,7 @@ export interface ProductDetails_product_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductDetails_product_attributes {
@ -110,6 +112,7 @@ export interface ProductDetails_product_productType_variantAttributes_choices_ed
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductDetails_product_productType_variantAttributes_choices_edges {

View file

@ -94,6 +94,7 @@ export interface ProductList_products_edges_node_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductList_products_edges_node_attributes {

View file

@ -34,6 +34,7 @@ export interface ProductType_productType_productAttributes_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductType_productType_productAttributes_choices_edges {

View file

@ -41,6 +41,7 @@ export interface ProductUpdate_productUpdate_product_attributes_attribute_choice
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes_attribute_choices_edges {
@ -84,6 +85,7 @@ export interface ProductUpdate_productUpdate_product_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductUpdate_productUpdate_product_attributes {
@ -117,6 +119,7 @@ export interface ProductUpdate_productUpdate_product_productType_variantAttribut
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductUpdate_productUpdate_product_productType_variantAttributes_choices_edges {

View file

@ -53,6 +53,7 @@ export interface ProductVariantCreateData_product_productType_selectionVariantAt
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantCreateData_product_productType_selectionVariantAttributes_choices_edges {
@ -104,6 +105,7 @@ export interface ProductVariantCreateData_product_productType_nonSelectionVarian
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantCreateData_product_productType_nonSelectionVariantAttributes_choices_edges {

View file

@ -46,6 +46,7 @@ export interface ProductVariantDetails_productVariant_selectionAttributes_attrib
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantDetails_productVariant_selectionAttributes_attribute_choices_edges {
@ -89,6 +90,7 @@ export interface ProductVariantDetails_productVariant_selectionAttributes_values
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantDetails_productVariant_selectionAttributes {
@ -122,6 +124,7 @@ export interface ProductVariantDetails_productVariant_nonSelectionAttributes_att
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantDetails_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -165,6 +168,7 @@ export interface ProductVariantDetails_productVariant_nonSelectionAttributes_val
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface ProductVariantDetails_productVariant_nonSelectionAttributes {

View file

@ -41,6 +41,7 @@ export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_choices_edges {
@ -84,6 +85,7 @@ export interface SimpleProductUpdate_productUpdate_product_attributes_values {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_attributes {
@ -117,6 +119,7 @@ export interface SimpleProductUpdate_productUpdate_product_productType_variantAt
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_productType_variantAttributes_choices_edges {
@ -389,6 +392,7 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_selecti
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantUpdate_productVariant_selectionAttributes_attribute_choices_edges {
@ -432,6 +436,7 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_selecti
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantUpdate_productVariant_selectionAttributes {
@ -465,6 +470,7 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSele
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -508,6 +514,7 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSele
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantUpdate_productVariant_nonSelectionAttributes {
@ -732,6 +739,7 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_selectionAttributes_attribute_choices_edges {
@ -775,6 +783,7 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_selectionAttributes {
@ -808,6 +817,7 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -851,6 +861,7 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_nonSelectionAttributes {
@ -1074,6 +1085,7 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_selectionAttributes_attribute_choices_edges {
@ -1117,6 +1129,7 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_selectionAttributes {
@ -1150,6 +1163,7 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -1193,6 +1207,7 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_nonSelectionAttributes {
@ -1417,6 +1432,7 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_selectionAttributes_attribute_choices_edges {
@ -1460,6 +1476,7 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_s
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_selectionAttributes {
@ -1493,6 +1510,7 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -1536,6 +1554,7 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_n
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes {

View file

@ -53,6 +53,7 @@ export interface VariantCreate_productVariantCreate_productVariant_selectionAttr
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantCreate_productVariantCreate_productVariant_selectionAttributes_attribute_choices_edges {
@ -96,6 +97,7 @@ export interface VariantCreate_productVariantCreate_productVariant_selectionAttr
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantCreate_productVariantCreate_productVariant_selectionAttributes {
@ -129,6 +131,7 @@ export interface VariantCreate_productVariantCreate_productVariant_nonSelectionA
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantCreate_productVariantCreate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -172,6 +175,7 @@ export interface VariantCreate_productVariantCreate_productVariant_nonSelectionA
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantCreate_productVariantCreate_productVariant_nonSelectionAttributes {

View file

@ -53,6 +53,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttr
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttributes_attribute_choices_edges {
@ -96,6 +97,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttr
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantUpdate_productVariant_selectionAttributes {
@ -129,6 +131,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionA
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -172,6 +175,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionA
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantUpdate_productVariant_nonSelectionAttributes {
@ -396,6 +400,7 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_selecti
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantStocksUpdate_productVariant_selectionAttributes_attribute_choices_edges {
@ -439,6 +444,7 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_selecti
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantStocksUpdate_productVariant_selectionAttributes {
@ -472,6 +478,7 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSele
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes_attribute_choices_edges {
@ -515,6 +522,7 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSele
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface VariantUpdate_productVariantStocksUpdate_productVariant_nonSelectionAttributes {

View file

@ -24,6 +24,7 @@ export interface SearchAttributeValues_attribute_choices_edges_node {
boolean: boolean | null;
date: any | null;
dateTime: any | null;
value: string | null;
}
export interface SearchAttributeValues_attribute_choices_edges {

View file

@ -61,7 +61,7 @@ exports[`Storyshots Attributes / Attributes default 1`] = `
<div
class="MuiTypography-root-id Attributes-expansionBarLabel-id MuiTypography-caption-id"
>
9 Attributes
10 Attributes
</div>
</div>
<button
@ -628,6 +628,87 @@ exports[`Storyshots Attributes / Attributes default 1`] = `
</div>
</div>
</div>
<hr
class="Hr-root-id"
/>
<div
class="BasicAttributeRow-attributeSection-id Grid-root-id Grid-uniform-id"
>
<div
class="BasicAttributeRow-attributeSectionLabel-id"
data-test="attribute-label"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
Swatch Attribute
</div>
</div>
<div
class=""
data-test="attribute-value"
>
<div
class="SingleAutocompleteSelectField-container-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
aria-autocomplete="list"
aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox"
>
<div
class="MuiInputAdornment-root-id MuiInputAdornment-positionStart-id"
>
<div
class="AttributeRow-swatchPreview-id"
/>
</div>
<input
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id AttributeRow-swatchInput-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text"
value=""
/>
<div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
viewBox="0 0 24 24"
>
<g
style="fill-rule:evenodd"
>
<path
d="M7 10l5 5 5-5z"
/>
</g>
</svg>
</div>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -670,7 +751,7 @@ exports[`Storyshots Attributes / Attributes disabled 1`] = `
<div
class="MuiTypography-root-id Attributes-expansionBarLabel-id MuiTypography-caption-id"
>
9 Attributes
10 Attributes
</div>
</div>
<button
@ -1247,6 +1328,88 @@ exports[`Storyshots Attributes / Attributes disabled 1`] = `
</div>
</div>
</div>
<hr
class="Hr-root-id"
/>
<div
class="BasicAttributeRow-attributeSection-id Grid-root-id Grid-uniform-id"
>
<div
class="BasicAttributeRow-attributeSectionLabel-id"
data-test="attribute-label"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
Swatch Attribute
</div>
</div>
<div
class=""
data-test="attribute-value"
>
<div
class="SingleAutocompleteSelectField-container-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
aria-autocomplete="list"
aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox"
>
<div
class="MuiInputAdornment-root-id MuiInputAdornment-positionStart-id"
>
<div
class="AttributeRow-swatchPreview-id"
/>
</div>
<input
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id AttributeRow-swatchInput-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
disabled=""
type="text"
value=""
/>
<div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
viewBox="0 0 24 24"
>
<g
style="fill-rule:evenodd"
>
<path
d="M7 10l5 5 5-5z"
/>
</g>
</svg>
</div>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -1289,7 +1452,7 @@ exports[`Storyshots Attributes / Attributes selected 1`] = `
<div
class="MuiTypography-root-id Attributes-expansionBarLabel-id MuiTypography-caption-id"
>
9 Attributes
10 Attributes
</div>
</div>
<button
@ -2125,6 +2288,88 @@ exports[`Storyshots Attributes / Attributes selected 1`] = `
</div>
</div>
</div>
<hr
class="Hr-root-id"
/>
<div
class="BasicAttributeRow-attributeSection-id Grid-root-id Grid-uniform-id"
>
<div
class="BasicAttributeRow-attributeSectionLabel-id"
data-test="attribute-label"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
Swatch Attribute
</div>
</div>
<div
class=""
data-test="attribute-value"
>
<div
class="SingleAutocompleteSelectField-container-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<div
aria-autocomplete="list"
aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox"
>
<div
class="MuiInputAdornment-root-id MuiInputAdornment-positionStart-id"
>
<div
class="AttributeRow-swatchPreview-id"
style="background-color:#FF0000"
/>
</div>
<input
aria-invalid="false"
autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id AttributeRow-swatchInput-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text"
value="Red"
/>
<div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
viewBox="0 0 24 24"
>
<g
style="fill-rule:evenodd"
>
<path
d="M7 10l5 5 5-5z"
/>
</g>
</svg>
</div>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -5439,6 +5684,189 @@ exports[`Storyshots Generics / Chip with x 1`] = `
</div>
`;
exports[`Storyshots Generics / ColorPicker default 1`] = `
<div
style="margin:auto;overflow:visible;min-height:800px;width:400px;display:flex;justify-content:center;align-items:center"
>
<div
style="padding:24px"
>
<div
class="ColorPicker-picker-id"
>
<div>
<div
class="w-color-interactive w-color-saturation ColorPicker-saturation-id"
style="position:relative;inset:0;cursor:crosshair;background-image:linear-gradient(0deg, #000, transparent), linear-gradient(90deg, #fff, hsl(0, 100%, 50%));width:200px;height:200px;border-radius:0;touch-action:none"
tabindex="0"
>
<div
class="w-color-saturation-pointer "
style="position:absolute;top:100%;left:0%"
>
<div
class="w-color-saturation-fill"
style="width:6px;height:6px;transform:translate(-3px, -3px);box-shadow:rgb(255 255 255) 0px 0px 0px 1.5px, rgb(0 0 0 / 30%) 0px 0px 1px 1px inset, rgb(0 0 0 / 40%) 0px 0px 1px 2px;border-radius:50%;background-color:hsla(0, 0%, 0%, 1)"
/>
</div>
</div>
</div>
<div
class="HorizontalSpacer-container-id HorizontalSpacer-container-id"
/>
<div>
<div
class="w-color-alpha w-color-alpha-vertical w-color-hue "
style="border-radius:0;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==) left center;background-color:#fff;position:relative;width:16px;height:220px"
>
<div
style="inset:0;position:absolute;background:linear-gradient(to bottom, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%);border-radius:0"
/>
<div
class="w-color-interactive"
style="inset:0;z-index:1;position:absolute;touch-action:none"
tabindex="0"
>
<div
class="w-color-alpha-pointer "
style="position:absolute;top:0%"
>
<div
class="w-color-alpha-fill"
style="width:18px;height:18px;transform:translate(-1px, -9px);box-shadow:rgb(0 0 0 / 37%) 0px 1px 4px 0px;border-radius:50%;background-color:rgb(248, 248, 248)"
/>
</div>
</div>
</div>
</div>
<div
class="HorizontalSpacer-container-id HorizontalSpacer-container-id"
/>
<div>
<div
class="MuiFormControl-root-id MuiTextField-root-id ColorPicker-colorInput-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id"
>
R
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id"
type="text"
value="0"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root-id MuiTextField-root-id ColorPicker-colorInput-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id"
>
G
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id"
type="text"
value="0"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root-id MuiTextField-root-id ColorPicker-colorInput-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id"
>
B
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id"
type="text"
value="0"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root-id MuiTextField-root-id ColorPicker-colorInput-id"
>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-formControl-id MuiInputBase-adornedStart-id MuiOutlinedInput-adornedStart-id"
>
HEX
<input
aria-invalid="false"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedStart-id MuiOutlinedInput-inputAdornedStart-id"
maxlength="6"
pattern="[A-Za-z0-9]{6}"
type="text"
value="#000000"
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Generics / Column picker default 1`] = `
<div
style="padding:24px"

View file

@ -12,6 +12,7 @@ import React from "react";
import Decorator from "../../Decorator";
const props: AttributePageProps = {
children: () => null,
attribute,
disabled: false,
errors: [],
@ -34,16 +35,22 @@ const props: AttributePageProps = {
storiesOf("Views / Attributes / Attribute details", module)
.addDecorator(Decorator)
.add("default", () => <AttributePage {...props} />)
.add("default", () => <AttributePage {...props}>{() => null}</AttributePage>)
.add("loading", () => (
<AttributePage
{...props}
attribute={undefined}
disabled={true}
values={undefined}
/>
>
{() => null}
</AttributePage>
))
.add("no values", () => (
<AttributePage {...props} values={undefined}>
{() => null}
</AttributePage>
))
.add("no values", () => <AttributePage {...props} values={undefined} />)
.add("form errors", () => (
<AttributePage
{...props}
@ -52,7 +59,9 @@ storiesOf("Views / Attributes / Attribute details", module)
code: AttributeErrorCode.INVALID,
field
}))}
/>
>
{() => null}
</AttributePage>
))
.add("multiple select input", () => (
<AttributePage
@ -61,6 +70,12 @@ storiesOf("Views / Attributes / Attribute details", module)
...attribute,
inputType: AttributeInputTypeEnum.MULTISELECT
}}
/>
>
{() => null}
</AttributePage>
))
.add("create", () => <AttributePage {...props} attribute={null} />);
.add("create", () => (
<AttributePage {...props} attribute={null}>
{() => null}
</AttributePage>
));

View file

@ -111,6 +111,7 @@ export enum AttributeInputTypeEnum {
NUMERIC = "NUMERIC",
REFERENCE = "REFERENCE",
RICH_TEXT = "RICH_TEXT",
SWATCH = "SWATCH",
}
export enum AttributeSortField {
@ -1959,7 +1960,7 @@ export interface AttributeUpdateInput {
slug?: string | null;
unit?: MeasurementUnitsEnum | null;
removeValues?: (string | null)[] | null;
addValues?: (AttributeValueCreateInput | null)[] | null;
addValues?: (AttributeValueUpdateInput | null)[] | null;
valueRequired?: boolean | null;
isVariantOnly?: boolean | null;
visibleInStorefront?: boolean | null;
@ -1970,9 +1971,11 @@ export interface AttributeUpdateInput {
}
export interface AttributeValueCreateInput {
name: string;
value?: string | null;
richText?: any | null;
fileUrl?: string | null;
contentType?: string | null;
name: string;
}
export interface AttributeValueInput {
@ -1992,6 +1995,14 @@ export interface AttributeValueTranslationInput {
richText?: any | null;
}
export interface AttributeValueUpdateInput {
value?: string | null;
richText?: any | null;
fileUrl?: string | null;
contentType?: string | null;
name?: string | null;
}
export interface BulkAttributeValueInput {
id?: string | null;
values?: string[] | null;