1863 - Support reference attribute reordering (#946)

* Support reference attribute reordering

* Update attribute handlers tests
This commit is contained in:
Dawid Tarasiuk 2021-01-18 16:37:38 +01:00 committed by Jakub Majorek
parent 4b8c2ea5e7
commit b07bb08ade
19 changed files with 615 additions and 297 deletions

View file

@ -1,9 +1,8 @@
import { createAttributeMultiChangeHandler } from "@saleor/attributes/utils/handlers";
import { AttributeInputData } from "@saleor/components/Attributes";
import { FormsetData } from "@saleor/hooks/useFormset";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { createAttributeMultiChangeHandler } from "./handlers";
const attributes: FormsetData<AttributeInputData, string[]> = [
{
data: {

View file

@ -1,14 +1,23 @@
import { AttributeInput } from "@saleor/components/Attributes";
import {
AttributeInput,
AttributeInputData
} from "@saleor/components/Attributes";
import {
FileUpload,
FileUploadVariables
} from "@saleor/files/types/FileUpload";
import { FormsetData } from "@saleor/hooks/useFormset";
import {
FormsetAtomicData,
FormsetChange,
FormsetData
} from "@saleor/hooks/useFormset";
import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails";
import { ReorderEvent } from "@saleor/types";
import {
AttributeInputTypeEnum,
AttributeValueInput
} from "@saleor/types/globalTypes";
import { move, toggle } from "@saleor/utils/lists";
import { MutationFetchResult } from "react-apollo";
import {
@ -17,6 +26,99 @@ import {
} from "../types/AttributeValueDelete";
import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data";
export function createAttributeChangeHandler(
changeAttributeData: FormsetChange<string[]>,
triggerChange: () => void
): FormsetChange<string> {
return (attributeId: string, value: string) => {
triggerChange();
changeAttributeData(attributeId, value === "" ? [] : [value]);
};
}
export function createAttributeMultiChangeHandler(
changeAttributeData: FormsetChange<string[]>,
attributes: FormsetData<AttributeInputData, string[]>,
triggerChange: () => void
): FormsetChange<string> {
return (attributeId: string, value: string) => {
const attribute = attributes.find(
attribute => attribute.id === attributeId
);
const newAttributeValues = toggle(
value,
attribute.value,
(a, b) => a === b
);
triggerChange();
changeAttributeData(attributeId, newAttributeValues);
};
}
export function createAttributeReferenceChangeHandler(
changeAttributeData: FormsetChange<string[]>,
triggerChange: () => void
): FormsetChange<string[]> {
return (attributeId: string, values: string[]) => {
changeAttributeData(attributeId, values);
triggerChange();
};
}
export function createAttributeFileChangeHandler(
changeAttributeData: FormsetChange<string[]>,
attributesWithNewFileValue: FormsetData<FormsetData<null, File>>,
addAttributeNewFileValue: (data: FormsetAtomicData<null, File>) => void,
changeAttributeNewFileValue: FormsetChange<File>,
triggerChange: () => void
): FormsetChange<File> {
return (attributeId: string, value: File) => {
triggerChange();
const newFileValueAssigned = attributesWithNewFileValue.find(
attribute => attribute.id === attributeId
);
if (newFileValueAssigned) {
changeAttributeNewFileValue(attributeId, value);
} else {
addAttributeNewFileValue({
data: null,
id: attributeId,
label: null,
value
});
}
changeAttributeData(attributeId, value ? [value.name] : []);
};
}
export function createAttributeValueReorderHandler(
changeAttributeData: FormsetChange<string[]>,
attributes: FormsetData<AttributeInputData, string[]>,
triggerChange: () => void
): FormsetChange<ReorderEvent> {
return (attributeId: string, reorder: ReorderEvent) => {
triggerChange();
const attribute = attributes.find(
attribute => attribute.id === attributeId
);
const reorderedValues = move(
attribute.value[reorder.oldIndex],
attribute.value,
(a, b) => a === b,
reorder.newIndex
);
changeAttributeData(attributeId, reorderedValues);
};
}
interface AttributesArgs {
attributes: AttributeInput[];
updatedFileAttributes: AttributeValueInput[];

View file

@ -17,7 +17,7 @@ import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErr
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { ReorderAction } from "@saleor/types";
import { ReorderEvent } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { getProductErrorMessage } from "@saleor/utils/errors";
import getPageErrorMessage from "@saleor/utils/errors/page";
@ -56,12 +56,12 @@ export interface AttributesProps {
ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment
>;
title?: React.ReactNode;
onChange: FormsetChange;
onMultiChange: FormsetChange;
onFileChange: FormsetChange;
onReferencesRemove?: FormsetChange; // TODO: temporairy optional, should be changed to required, after all pages implement it
onReferencesAddClick?: (attribute: AttributeInput) => void; // TODO: temporairy optional, should be changed to required, after all pages implement it
onReferencesReorder?: ReorderAction; // TODO: temporairy optional, should be changed to required, after all pages implement it
onChange: FormsetChange<string>;
onMultiChange: FormsetChange<string>;
onFileChange: FormsetChange<File>;
onReferencesRemove: FormsetChange<string[]>;
onReferencesAddClick: (attribute: AttributeInput) => void;
onReferencesReorder: FormsetChange<ReorderEvent>;
}
const useStyles = makeStyles(
@ -328,8 +328,12 @@ const Attributes: React.FC<AttributesProps> = ({
attribute.value?.filter(id => id !== value)
)
}
onValueReorder={onReferencesReorder}
onValueReorder={event =>
onReferencesReorder(attribute.id, event)
}
loading={loading}
error={!!error}
helperText={getErrorMessage(error, intl)}
/>
</ExtendedAttributeRow>
) : attribute.data.inputType ===

View file

@ -24,4 +24,11 @@ storiesOf("Generics / Sortable chips field", module)
.addDecorator(CardDecorator)
.addDecorator(Decorator)
.add("default", () => <SortableChipsField {...props} />)
.add("loading", () => <SortableChipsField {...props} loading={true} />);
.add("loading", () => <SortableChipsField {...props} loading={true} />)
.add("with error", () => (
<SortableChipsField
{...props}
error={true}
helperText="Something went wrong"
/>
));

View file

@ -1,4 +1,5 @@
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import { ReorderAction } from "@saleor/types";
import React from "react";
import { SortableContainerProps } from "react-sortable-hoc";
@ -13,6 +14,9 @@ const useStyles = makeStyles(
background: "#fff",
color: theme.palette.primary.dark,
marginBottom: theme.spacing(1)
},
errorText: {
color: theme.palette.error.light
}
}),
{
@ -28,12 +32,21 @@ export interface SortableChipsFieldValueType {
export interface SortableChipsFieldProps extends SortableContainerProps {
loading?: boolean;
values: SortableChipsFieldValueType[];
error?: boolean;
helperText?: string;
onValueDelete: (id: string) => void;
onValueReorder: ReorderAction;
}
const SortableChipsField: React.FC<SortableChipsFieldProps> = props => {
const { loading, values, onValueDelete, onValueReorder } = props;
const {
loading,
values,
error,
helperText,
onValueDelete,
onValueReorder
} = props;
const classes = useStyles(props);
return (
@ -57,6 +70,11 @@ const SortableChipsField: React.FC<SortableChipsFieldProps> = props => {
/>
))
)}
{error && (
<Typography variant="caption" className={classes.errorText}>
{helperText}
</Typography>
)}
</div>
</SortableContainer>
);

View file

@ -148,6 +148,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
)}
<CardSpacer />

View file

@ -1,5 +1,12 @@
import { OutputData } from "@editorjs/editorjs";
import { getAttributesDisplayData } from "@saleor/attributes/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createAttributeValueReorderHandler
} from "@saleor/attributes/utils/handlers";
import { AttributeInput } from "@saleor/components/Attributes";
import { MetadataFormData } from "@saleor/components/Metadata";
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
@ -16,13 +23,8 @@ import {
} from "@saleor/pages/types/PageDetails";
import { getAttributeInputFromPage } from "@saleor/pages/utils/data";
import { createPageTypeSelectHandler } from "@saleor/pages/utils/handlers";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler
} from "@saleor/products/utils/handlers";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { ReorderEvent } from "@saleor/types";
import getPublicationData from "@saleor/utils/data/getPublicationData";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
@ -59,6 +61,7 @@ export interface PageUpdateHandlers {
selectAttributeMulti: FormsetChange<string>;
selectAttributeReference: FormsetChange<string[]>;
selectAttributeFile: FormsetChange<File>;
reorderAttributeValue: FormsetChange<ReorderEvent>;
}
export interface UsePageUpdateFormResult {
change: FormChange;
@ -150,6 +153,11 @@ function usePageForm(
attributesWithNewFileValue.change,
triggerChange
);
const handleAttributeValueReorder = createAttributeValueReorderHandler(
attributes.change,
attributes.data,
triggerChange
);
// Need to make it function to always have content.current up to date
const getData = (): PageData => ({
@ -190,6 +198,7 @@ function usePageForm(
handlers: {
changeContent,
changeMetadata,
reorderAttributeValue: handleAttributeValueReorder,
selectAttribute: handleAttributeChange,
selectAttributeFile: handleAttributeFileChange,
selectAttributeMulti: handleAttributeMultiChange,

View file

@ -1,7 +1,6 @@
import { AttributeInputData } from "@saleor/components/Attributes";
import { FormChange } from "@saleor/hooks/useForm";
import { FormsetChange, FormsetData } from "@saleor/hooks/useFormset";
import { toggle } from "@saleor/utils/lists";
import { FormsetData } from "@saleor/hooks/useFormset";
import { PageDetails_page_pageType } from "../types/PageDetails";
import { getAttributeInputFromPageType } from "./data";
@ -23,34 +22,3 @@ export function createPageTypeSelectHandler(
setAttributes(getAttributeInputFromPageType(selectedPageType));
};
}
export function createAttributeChangeHandler(
changeAttributeData: FormsetChange<string[]>,
triggerChange: () => void
): FormsetChange {
return (attributeId: string, value: string) => {
triggerChange();
changeAttributeData(attributeId, value === "" ? [] : [value]);
};
}
export function createAttributeMultiChangeHandler(
changeAttributeData: FormsetChange<string[]>,
attributes: FormsetData<AttributeInputData, string[]>,
triggerChange: () => void
): FormsetChange {
return (attributeId: string, value: string) => {
const attribute = attributes.find(
attribute => attribute.id === attributeId
);
const newAttributeValues = toggle(
value,
attribute.value,
(a, b) => a === b
);
triggerChange();
changeAttributeData(attributeId, newAttributeValues);
};
}

View file

@ -206,6 +206,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
)}
<CardSpacer />

View file

@ -1,5 +1,12 @@
import { OutputData } from "@editorjs/editorjs";
import { getAttributesDisplayData } from "@saleor/attributes/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createAttributeValueReorderHandler
} from "@saleor/attributes/utils/handlers";
import { ChannelData, ChannelPriceArgs } from "@saleor/channels/utils";
import {
AttributeInput,
@ -20,10 +27,6 @@ import {
ProductType
} from "@saleor/products/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createChannelsChangeHandler,
createChannelsPriceChangeHandler,
createProductTypeSelectHandler
@ -35,6 +38,7 @@ import {
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchProductTypes_search_edges_node } from "@saleor/searches/types/SearchProductTypes";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { ReorderEvent } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
@ -92,6 +96,7 @@ export interface ProductCreateHandlers
>,
Record<"selectAttributeReference", FormsetChange<string[]>>,
Record<"selectAttributeFile", FormsetChange<File>>,
Record<"reorderAttributeValue", FormsetChange<ReorderEvent>>,
Record<"addStock" | "deleteStock", (id: string) => void> {
changeDescription: RichTextEditorChange;
}
@ -224,6 +229,11 @@ function useProductCreateForm(
attributesWithNewFileValue.change,
triggerChange
);
const handleAttributeValueReorder = createAttributeValueReorderHandler(
attributes.change,
attributes.data,
triggerChange
);
const handleProductTypeSelect = createProductTypeSelectHandler(
attributes.set,
setProductType,
@ -305,6 +315,7 @@ function useProductCreateForm(
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
reorderAttributeValue: handleAttributeValueReorder,
selectAttribute: handleAttributeChange,
selectAttributeFile: handleAttributeFileChange,
selectAttributeMultiple: handleAttributeMultiChange,

View file

@ -267,6 +267,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
)}
<CardSpacer />

View file

@ -1,5 +1,12 @@
import { OutputData } from "@editorjs/editorjs";
import { getAttributesDisplayData } from "@saleor/attributes/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createAttributeValueReorderHandler
} from "@saleor/attributes/utils/handlers";
import { ChannelData, ChannelPriceArgs } from "@saleor/channels/utils";
import { AttributeInput } from "@saleor/components/Attributes";
import { MetadataFormData } from "@saleor/components/Metadata";
@ -19,10 +26,6 @@ import {
getStockInputFromProduct
} from "@saleor/products/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createChannelsChangeHandler,
createChannelsPriceChangeHandler
} from "@saleor/products/utils/handlers";
@ -32,6 +35,7 @@ import {
} from "@saleor/products/utils/validation";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { ReorderEvent } from "@saleor/types";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
@ -109,6 +113,7 @@ export interface ProductUpdateHandlers
>,
Record<"selectAttributeReference", FormsetChange<string[]>>,
Record<"selectAttributeFile", FormsetChange<File>>,
Record<"reorderAttributeValue", FormsetChange<ReorderEvent>>,
Record<"addStock" | "deleteStock", (id: string) => void> {
changeDescription: RichTextEditorChange;
}
@ -234,6 +239,11 @@ function useProductUpdateForm(
attributesWithNewFileValue.change,
triggerChange
);
const handleAttributeValueReorder = createAttributeValueReorderHandler(
attributes.change,
attributes.data,
triggerChange
);
const handleStockChange: FormsetChange<string> = (id, value) => {
triggerChange();
stocks.change(id, value);
@ -322,6 +332,7 @@ function useProductUpdateForm(
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
reorderAttributeValue: handleAttributeValueReorder,
selectAttribute: handleAttributeChange,
selectAttributeFile: handleAttributeFileChange,
selectAttributeMultiple: handleAttributeMultiChange,

View file

@ -162,6 +162,9 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
<CardSpacer />
<Attributes
@ -179,6 +182,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
<CardSpacer />
<ProductShipping

View file

@ -1,4 +1,11 @@
import { getAttributesDisplayData } from "@saleor/attributes/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createAttributeValueReorderHandler
} from "@saleor/attributes/utils/handlers";
import { ChannelPriceData, IChannelPriceArgs } from "@saleor/channels/utils";
import { AttributeInput } from "@saleor/components/Attributes";
import { MetadataFormData } from "@saleor/components/Metadata";
@ -9,21 +16,14 @@ import useFormset, {
} from "@saleor/hooks/useFormset";
import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData";
import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data";
import {
createAttributeFileChangeHandler,
createAttributeReferenceChangeHandler,
getChannelsInput
} from "@saleor/products/utils/handlers";
import {
createAttributeChangeHandler,
createAttributeMultiChangeHandler
} from "@saleor/products/utils/handlers";
import { getChannelsInput } from "@saleor/products/utils/handlers";
import {
validateCostPrice,
validatePrice
} from "@saleor/products/utils/validation";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { ReorderEvent } from "@saleor/types";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import React from "react";
@ -57,6 +57,7 @@ export interface ProductVariantCreateHandlers
>,
Record<"selectAttributeReference", FormsetChange<string[]>>,
Record<"selectAttributeFile", FormsetChange<File>>,
Record<"reorderAttributeValue", FormsetChange<ReorderEvent>>,
Record<"addStock" | "deleteStock", (id: string) => void> {
changeMetadata: FormChange;
}
@ -131,6 +132,11 @@ function useProductVariantCreateForm(
attributesWithNewFileValue.change,
triggerChange
);
const handleAttributeValueReorder = createAttributeValueReorderHandler(
attributes.change,
attributes.data,
triggerChange
);
const handleStockAdd = (id: string) => {
triggerChange();
stocks.add({
@ -183,6 +189,7 @@ function useProductVariantCreateForm(
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
reorderAttributeValue: handleAttributeValueReorder,
selectAttribute: handleAttributeChange,
selectAttributeFile: handleAttributeFileChange,
selectAttributeMultiple: handleAttributeMultiChange,

View file

@ -217,6 +217,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
<CardSpacer />
<Attributes
@ -236,6 +237,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
/>
<CardSpacer />
<ProductVariantImages

View file

@ -1,4 +1,11 @@
import { getAttributesDisplayData } from "@saleor/attributes/utils/data";
import {
createAttributeChangeHandler,
createAttributeFileChangeHandler,
createAttributeMultiChangeHandler,
createAttributeReferenceChangeHandler,
createAttributeValueReorderHandler
} from "@saleor/attributes/utils/handlers";
import { ChannelPriceData, IChannelPriceArgs } from "@saleor/channels/utils";
import { AttributeInput } from "@saleor/components/Attributes";
import { MetadataFormData } from "@saleor/components/Metadata";
@ -12,21 +19,14 @@ import {
getAttributeInputFromVariant,
getStockInputFromVariant
} from "@saleor/products/utils/data";
import {
createAttributeFileChangeHandler,
createAttributeReferenceChangeHandler,
getChannelsInput
} from "@saleor/products/utils/handlers";
import {
createAttributeChangeHandler,
createAttributeMultiChangeHandler
} from "@saleor/products/utils/handlers";
import { getChannelsInput } from "@saleor/products/utils/handlers";
import {
validateCostPrice,
validatePrice
} from "@saleor/products/utils/validation";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { ReorderEvent } from "@saleor/types";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import getMetadata from "@saleor/utils/metadata/getMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
@ -72,6 +72,7 @@ export interface ProductVariantUpdateHandlers
>,
Record<"selectAttributeReference", FormsetChange<string[]>>,
Record<"selectAttributeFile", FormsetChange<File>>,
Record<"reorderAttributeValue", FormsetChange<ReorderEvent>>,
Record<"addStock" | "deleteStock", (id: string) => void> {
changeMetadata: FormChange;
}
@ -148,6 +149,11 @@ function useProductVariantUpdateForm(
attributesWithNewFileValue.change,
triggerChange
);
const handleAttributeValueReorder = createAttributeValueReorderHandler(
attributes.change,
attributes.data,
triggerChange
);
const handleStockAdd = (id: string) => {
triggerChange();
stocks.add({
@ -233,6 +239,7 @@ function useProductVariantUpdateForm(
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
reorderAttributeValue: handleAttributeValueReorder,
selectAttribute: handleAttributeChange,
selectAttributeFile: handleAttributeFileChange,
selectAttributeMultiple: handleAttributeMultiChange,

View file

@ -1,142 +0,0 @@
import { AttributeInputData } from "@saleor/components/Attributes";
import { FormsetData } from "@saleor/hooks/useFormset";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { createAttributeMultiChangeHandler } from "./handlers";
const attributes: FormsetData<AttributeInputData, string[]> = [
{
data: {
inputType: AttributeInputTypeEnum.DROPDOWN,
isRequired: false,
values: [
{
__typename: "AttributeValue",
file: null,
id: "attrv-1",
name: "Attribute 1 Value 1",
reference: null,
slug: "attr-1-v-1"
}
]
},
id: "attr-1",
label: "Attribute 1",
value: []
},
{
data: {
inputType: AttributeInputTypeEnum.MULTISELECT,
isRequired: false,
values: [
{
__typename: "AttributeValue",
file: null,
id: "attrv-2",
name: "Attribute 2 Value 1",
reference: null,
slug: "attr-2-v-1"
},
{
__typename: "AttributeValue",
file: null,
id: "attrv-3",
name: "Attribute 2 Value 2",
reference: null,
slug: "attr-2-v-2"
},
{
__typename: "AttributeValue",
file: null,
id: "attrv-4",
name: "Attribute 2 Value 3",
reference: null,
slug: "attr-2-v-3"
}
]
},
id: "attr-2",
label: "Attribute 2",
value: ["attr-2-v-3"]
},
{
data: {
inputType: AttributeInputTypeEnum.FILE,
isRequired: false,
values: [
{
__typename: "AttributeValue",
file: {
__typename: "File",
contentType: "image/png",
url: "some-non-existing-url"
},
id: "attrv-5",
name: "File First Value",
reference: null,
slug: "file-first-value"
}
]
},
id: "attr-3",
label: "File Attribute",
value: []
}
];
describe("Multiple select", () => {
it("is able to select value", () => {
const change = jest.fn();
const trigger = jest.fn();
const handler = createAttributeMultiChangeHandler(
change,
attributes,
trigger
);
handler("attr-2", "attr-2-v-1");
expect(change).toHaveBeenCalledTimes(1);
expect(change.mock.calls[0][0]).toBe("attr-2");
expect(change.mock.calls[0][1]).toHaveLength(2);
expect(change.mock.calls[0][1][0]).toBe("attr-2-v-3");
expect(change.mock.calls[0][1][1]).toBe("attr-2-v-1");
expect(trigger).toHaveBeenCalledTimes(1);
});
it("is able to deselect value", () => {
const change = jest.fn();
const trigger = jest.fn();
const handler = createAttributeMultiChangeHandler(
change,
attributes,
trigger
);
handler("attr-2", "attr-2-v-3");
expect(change).toHaveBeenCalledTimes(1);
expect(change.mock.calls[0][0]).toBe("attr-2");
expect(change.mock.calls[0][1]).toHaveLength(0);
expect(trigger).toHaveBeenCalledTimes(1);
});
it("is able to add custom value", () => {
const change = jest.fn();
const trigger = jest.fn();
const handler = createAttributeMultiChangeHandler(
change,
attributes,
trigger
);
handler("attr-2", "A Value");
expect(change).toHaveBeenCalledTimes(1);
expect(change.mock.calls[0][0]).toBe("attr-2");
expect(change.mock.calls[0][1]).toHaveLength(2);
expect(change.mock.calls[0][1][0]).toBe("attr-2-v-3");
expect(change.mock.calls[0][1][1]).toBe("A Value");
expect(trigger).toHaveBeenCalledTimes(1);
});
});

View file

@ -5,25 +5,10 @@ import {
} from "@saleor/channels/utils";
import { AttributeInputData } from "@saleor/components/Attributes";
import { FormChange } from "@saleor/hooks/useForm";
import {
FormsetAtomicData,
FormsetChange,
FormsetData
} from "@saleor/hooks/useFormset";
import { toggle } from "@saleor/utils/lists";
import { FormsetData } from "@saleor/hooks/useFormset";
import { getAttributeInputFromProductType, ProductType } from "./data";
export function createAttributeChangeHandler(
changeAttributeData: FormsetChange<string[]>,
triggerChange: () => void
): FormsetChange<string> {
return (attributeId: string, value: string) => {
triggerChange();
changeAttributeData(attributeId, value === "" ? [] : [value]);
};
}
export function createChannelsPriceChangeHandler(
channelListings: ChannelPriceData[],
updateChannels: (data: ChannelPriceData[]) => void,
@ -103,66 +88,6 @@ export function createVariantChannelsChangeHandler(
};
}
export function createAttributeMultiChangeHandler(
changeAttributeData: FormsetChange<string[]>,
attributes: FormsetData<AttributeInputData, string[]>,
triggerChange: () => void
): FormsetChange<string> {
return (attributeId: string, value: string) => {
const attribute = attributes.find(
attribute => attribute.id === attributeId
);
const newAttributeValues = toggle(
value,
attribute.value,
(a, b) => a === b
);
triggerChange();
changeAttributeData(attributeId, newAttributeValues);
};
}
export function createAttributeReferenceChangeHandler(
changeAttributeData: FormsetChange<string[]>,
triggerChange: () => void
): FormsetChange<string[]> {
return (attributeId: string, values: string[]) => {
changeAttributeData(attributeId, values);
triggerChange();
};
}
export function createAttributeFileChangeHandler(
changeAttributeData: FormsetChange<string[]>,
attributesWithNewFileValue: FormsetData<FormsetData<null, File>>,
addAttributeNewFileValue: (data: FormsetAtomicData<null, File>) => void,
changeAttributeNewFileValue: FormsetChange<File>,
triggerChange: () => void
): FormsetChange<File> {
return (attributeId: string, value: File) => {
triggerChange();
const newFileValueAssigned = attributesWithNewFileValue.find(
attribute => attribute.id === attributeId
);
if (newFileValueAssigned) {
changeAttributeNewFileValue(attributeId, value);
} else {
addAttributeNewFileValue({
data: null,
id: attributeId,
label: null,
value
});
}
changeAttributeData(attributeId, value ? [value.name] : []);
};
}
export function createProductTypeSelectHandler(
setAttributes: (data: FormsetData<AttributeInputData>) => void,
setProductType: (productType: ProductType) => void,

View file

@ -11234,6 +11234,389 @@ exports[`Storyshots Generics / Sortable chips field loading 1`] = `
</div>
`;
exports[`Storyshots Generics / Sortable chips field with error 1`] = `
<div
style="padding:24px"
>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
style="margin:auto;overflow:visible;position:relative;width:400px"
>
<div
class="MuiCardContent-root-id"
>
<div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 1
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 2
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 3
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 4
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 5
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="SortableChip-root-id SortableChipsField-chip-id"
>
<div
class="SortableChip-content-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableHandle-drag-id SortableChip-sortableHandle-id"
data-test="button-drag-handle"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
clip-rule="evenodd"
d="M3.5 2C3.5 2.82843 2.82843 3.5 2 3.5C1.17157 3.5 0.5 2.82843 0.5 2C0.5 1.17157 1.17157 0.5 2 0.5C2.82843 0.5 3.5 1.17157 3.5 2ZM4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2ZM9.5 2C9.5 2.82843 8.82843 3.5 8 3.5C7.17157 3.5 6.5 2.82843 6.5 2C6.5 1.17157 7.17157 0.5 8 0.5C8.82843 0.5 9.5 1.17157 9.5 2ZM10 2C10 3.10457 9.10457 4 8 4C6.89543 4 6 3.10457 6 2C6 0.895431 6.89543 0 8 0C9.10457 0 10 0.895431 10 2ZM8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5ZM8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10ZM3.5 8C3.5 8.82843 2.82843 9.5 2 9.5C1.17157 9.5 0.5 8.82843 0.5 8C0.5 7.17157 1.17157 6.5 2 6.5C2.82843 6.5 3.5 7.17157 3.5 8ZM4 8C4 9.10457 3.10457 10 2 10C0.895431 10 0 9.10457 0 8C0 6.89543 0.895431 6 2 6C3.10457 6 4 6.89543 4 8ZM2 15.5C2.82843 15.5 3.5 14.8284 3.5 14C3.5 13.1716 2.82843 12.5 2 12.5C1.17157 12.5 0.5 13.1716 0.5 14C0.5 14.8284 1.17157 15.5 2 15.5ZM2 16C3.10457 16 4 15.1046 4 14C4 12.8954 3.10457 12 2 12C0.895431 12 0 12.8954 0 14C0 15.1046 0.895431 16 2 16ZM9.5 14C9.5 14.8284 8.82843 15.5 8 15.5C7.17157 15.5 6.5 14.8284 6.5 14C6.5 13.1716 7.17157 12.5 8 12.5C8.82843 12.5 9.5 13.1716 9.5 14ZM10 14C10 15.1046 9.10457 16 8 16C6.89543 16 6 15.1046 6 14C6 12.8954 6.89543 12 8 12C9.10457 12 10 12.8954 10 14Z"
fill="url(#paint0_linear)"
fill-rule="evenodd"
style="transform:translate(7px, 4px)"
/>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint0_linear"
x1="0"
x2="16.0896"
y1="0"
y2="10.4478"
>
<stop
stop-color="#06847B"
/>
<stop
offset="1"
stop-color="#3EE7CD"
/>
</linearGradient>
</defs>
</svg>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
data-test="chip-label"
>
Item 6
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id SortableChip-closeIcon-id"
data-test="button-close"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
</div>
</div>
<div
class="MuiTypography-root-id SortableChipsField-errorText-id MuiTypography-caption-id"
>
Something went wrong
</div>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Generics / Square Button default 1`] = `
<div
style="padding:24px"