1862 - Add references field to attributes section (#923)
* Add references field to attributes section * Update messagees and test shapshots * Remove unused style in sortable chips component
This commit is contained in:
parent
55a330040e
commit
ee05b090b8
19 changed files with 1468 additions and 176 deletions
|
@ -1543,6 +1543,10 @@
|
|||
"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"
|
||||
|
|
|
@ -393,6 +393,7 @@ type Attribute implements Node & ObjectWithMetadata {
|
|||
privateMetadata: [MetadataItem]!
|
||||
metadata: [MetadataItem]!
|
||||
inputType: AttributeInputTypeEnum
|
||||
entityType: AttributeEntityTypeEnum
|
||||
name: String
|
||||
slug: String
|
||||
type: AttributeTypeEnum
|
||||
|
@ -431,6 +432,7 @@ type AttributeCreate {
|
|||
|
||||
input AttributeCreateInput {
|
||||
inputType: AttributeInputTypeEnum
|
||||
entityType: AttributeEntityTypeEnum
|
||||
name: String!
|
||||
slug: String
|
||||
type: AttributeTypeEnum!
|
||||
|
@ -450,6 +452,10 @@ type AttributeDelete {
|
|||
attribute: Attribute
|
||||
}
|
||||
|
||||
enum AttributeEntityTypeEnum {
|
||||
PAGE
|
||||
}
|
||||
|
||||
type AttributeError {
|
||||
field: String
|
||||
message: String
|
||||
|
@ -490,6 +496,7 @@ enum AttributeInputTypeEnum {
|
|||
DROPDOWN
|
||||
MULTISELECT
|
||||
FILE
|
||||
REFERENCE
|
||||
}
|
||||
|
||||
type AttributeReorderValues {
|
||||
|
@ -566,6 +573,7 @@ type AttributeValue implements Node {
|
|||
type: AttributeValueType @deprecated(reason: "Use the `inputType` field to determine the type of attribute's value. This field will be removed after 2020-07-31.")
|
||||
translation(languageCode: LanguageCodeEnum!): AttributeValueTranslation
|
||||
inputType: AttributeInputTypeEnum
|
||||
reference: ID
|
||||
file: File
|
||||
}
|
||||
|
||||
|
@ -598,6 +606,7 @@ input AttributeValueInput {
|
|||
values: [String]
|
||||
file: String
|
||||
contentType: String
|
||||
references: [ID!]
|
||||
}
|
||||
|
||||
type AttributeValueTranslatableContent implements Node {
|
||||
|
@ -643,6 +652,7 @@ type BulkProductError {
|
|||
message: String
|
||||
code: ProductErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
index: Int
|
||||
warehouses: [ID!]
|
||||
channels: [ID!]
|
||||
|
@ -653,6 +663,7 @@ type BulkStockError {
|
|||
message: String
|
||||
code: ProductErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
index: Int
|
||||
}
|
||||
|
||||
|
@ -1082,6 +1093,7 @@ type CollectionChannelListingError {
|
|||
message: String
|
||||
code: ProductErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
channels: [ID!]
|
||||
}
|
||||
|
||||
|
@ -2596,6 +2608,7 @@ type Mutation {
|
|||
productTypeBulkDelete(ids: [ID]!): ProductTypeBulkDelete
|
||||
productTypeUpdate(id: ID!, input: ProductTypeInput!): ProductTypeUpdate
|
||||
productTypeReorderAttributes(moves: [ReorderInput]!, productTypeId: ID!, type: ProductAttributeType!): ProductTypeReorderAttributes
|
||||
productReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, productId: ID!): ProductReorderAttributeValues
|
||||
digitalContentCreate(input: DigitalContentUploadInput!, variantId: ID!): DigitalContentCreate
|
||||
digitalContentDelete(variantId: ID!): DigitalContentDelete
|
||||
digitalContentUpdate(input: DigitalContentInput!, variantId: ID!): DigitalContentUpdate
|
||||
|
@ -2611,6 +2624,7 @@ type Mutation {
|
|||
productVariantSetDefault(productId: ID!, variantId: ID!): ProductVariantSetDefault
|
||||
productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate
|
||||
productVariantChannelListingUpdate(id: ID!, input: [ProductVariantChannelListingAddInput!]!): ProductVariantChannelListingUpdate
|
||||
productVariantReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, variantId: ID!): ProductVariantReorderAttributeValues
|
||||
variantImageAssign(imageId: ID!, variantId: ID!): VariantImageAssign
|
||||
variantImageUnassign(imageId: ID!, variantId: ID!): VariantImageUnassign
|
||||
paymentCapture(amount: PositiveDecimal, paymentId: ID!): PaymentCapture
|
||||
|
@ -2630,6 +2644,7 @@ type Mutation {
|
|||
pageAttributeAssign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeAssign
|
||||
pageAttributeUnassign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeUnassign
|
||||
pageTypeReorderAttributes(moves: [ReorderInput!]!, pageTypeId: ID!): PageTypeReorderAttributes
|
||||
pageReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, pageId: ID!): PageReorderAttributeValues
|
||||
draftOrderComplete(id: ID!): DraftOrderComplete
|
||||
draftOrderCreate(input: DraftOrderCreateInput!): DraftOrderCreate
|
||||
draftOrderDelete(id: ID!): DraftOrderDelete
|
||||
|
@ -3290,6 +3305,7 @@ type PageError {
|
|||
message: String
|
||||
code: PageErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
}
|
||||
|
||||
enum PageErrorCode {
|
||||
|
@ -3324,6 +3340,12 @@ input PageInput {
|
|||
seo: SeoInput
|
||||
}
|
||||
|
||||
type PageReorderAttributeValues {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
page: Page
|
||||
pageErrors: [PageError!]!
|
||||
}
|
||||
|
||||
enum PageSortField {
|
||||
TITLE
|
||||
SLUG
|
||||
|
@ -3821,6 +3843,7 @@ type ProductChannelListingError {
|
|||
message: String
|
||||
code: ProductErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
channels: [ID!]
|
||||
}
|
||||
|
||||
|
@ -3879,6 +3902,7 @@ type ProductError {
|
|||
message: String
|
||||
code: ProductErrorCode!
|
||||
attributes: [ID!]
|
||||
values: [ID!]
|
||||
}
|
||||
|
||||
enum ProductErrorCode {
|
||||
|
@ -4026,6 +4050,12 @@ type ProductPricingInfo {
|
|||
priceRangeLocalCurrency: TaxedMoneyRange
|
||||
}
|
||||
|
||||
type ProductReorderAttributeValues {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
product: Product
|
||||
productErrors: [ProductError!]!
|
||||
}
|
||||
|
||||
input ProductStockFilterInput {
|
||||
warehouseIds: [ID!]
|
||||
quantity: IntRangeInput
|
||||
|
@ -4172,8 +4202,12 @@ type ProductVariant implements Node & ObjectWithMetadata {
|
|||
weight: Weight
|
||||
privateMetadata: [MetadataItem]!
|
||||
metadata: [MetadataItem]!
|
||||
quantity: Int! @deprecated(reason: "Use the stock field instead. This field will be removed after 2020-07-31.")
|
||||
quantityAllocated: Int @deprecated(reason: "Use the stock field instead. This field will be removed after 2020-07-31.")
|
||||
stockQuantity: Int! @deprecated(reason: "Use the quantityAvailable field instead. This field will be removed after 2020-07-31.")
|
||||
channelListings: [ProductVariantChannelListing!]
|
||||
pricing: VariantPricingInfo
|
||||
isAvailable: Boolean @deprecated(reason: "Use the stock field instead. This field will be removed after 2020-07-31.")
|
||||
attributes(variantSelection: VariantAttributeScope): [SelectedAttribute!]!
|
||||
costPrice: Money
|
||||
margin: Int
|
||||
|
@ -4278,6 +4312,12 @@ type ProductVariantReorder {
|
|||
productErrors: [ProductError!]!
|
||||
}
|
||||
|
||||
type ProductVariantReorderAttributeValues {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
productVariant: ProductVariant
|
||||
productErrors: [ProductError!]!
|
||||
}
|
||||
|
||||
type ProductVariantSetDefault {
|
||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||
product: Product
|
||||
|
|
|
@ -12,7 +12,10 @@ const props: AttributesProps = {
|
|||
loading: false,
|
||||
onChange: () => undefined,
|
||||
onFileChange: () => undefined,
|
||||
onMultiChange: () => undefined
|
||||
onMultiChange: () => undefined,
|
||||
onReferencesChange: () => undefined,
|
||||
onReferencesChangeClick: () => undefined,
|
||||
onReferencesReorder: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Attributes / Attributes", module)
|
||||
|
|
|
@ -5,7 +5,6 @@ import makeStyles from "@material-ui/core/styles/makeStyles";
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import MultiAutocompleteSelectField, {
|
||||
MultiAutocompleteChoiceType
|
||||
|
@ -17,6 +16,7 @@ import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFr
|
|||
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
|
||||
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
|
||||
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import getPageErrorMessage from "@saleor/utils/errors/page";
|
||||
|
@ -30,6 +30,9 @@ import {
|
|||
} from "react-intl";
|
||||
|
||||
import FileUploadField, { FileChoiceType } from "../FileUploadField";
|
||||
import SortableChipsField from "../SortableChipsField";
|
||||
import BasicAttributeRow from "./BasicAttributeRow";
|
||||
import ExtendedAttributeRow from "./ExtendedAttributeRow";
|
||||
import { VariantAttributeScope } from "./types";
|
||||
|
||||
export interface AttributeInputData {
|
||||
|
@ -51,7 +54,10 @@ export interface AttributesProps {
|
|||
title?: React.ReactNode;
|
||||
onChange: FormsetChange;
|
||||
onMultiChange: FormsetChange;
|
||||
onFileChange?: FormsetChange; // TODO: temporairy optional, should be changed to required, after all pages implement it
|
||||
onFileChange: FormsetChange;
|
||||
onReferencesChange?: FormsetChange; // TODO: temporairy optional, should be changed to required, after all pages implement it
|
||||
onReferencesChangeClick?: () => 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
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -210,7 +216,10 @@ const Attributes: React.FC<AttributesProps> = ({
|
|||
title,
|
||||
onChange,
|
||||
onMultiChange,
|
||||
onFileChange
|
||||
onFileChange,
|
||||
onReferencesChange,
|
||||
onReferencesChangeClick,
|
||||
onReferencesReorder
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
@ -255,24 +264,38 @@ const Attributes: React.FC<AttributesProps> = ({
|
|||
return (
|
||||
<React.Fragment key={attribute.id}>
|
||||
{attributeIndex > 0 && <Hr />}
|
||||
<Grid className={classes.attributeSection} variant="uniform">
|
||||
<div
|
||||
className={classes.attributeSectionLabel}
|
||||
data-test="attribute-label"
|
||||
>
|
||||
<Typography>{attribute.label}</Typography>
|
||||
</div>
|
||||
<div data-test="attribute-value">
|
||||
{attribute.data.inputType ===
|
||||
AttributeInputTypeEnum.REFERENCE ? (
|
||||
<ExtendedAttributeRow
|
||||
label={attribute.label}
|
||||
selectLabel={intl.formatMessage({
|
||||
defaultMessage: "Assign references",
|
||||
description: "button label"
|
||||
})}
|
||||
onSelect={onReferencesChangeClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SortableChipsField
|
||||
values={getMultiDisplayValue(attribute)}
|
||||
onValueDelete={value =>
|
||||
onReferencesChange(
|
||||
attribute.id,
|
||||
attribute.value?.filter(id => id !== value)
|
||||
)
|
||||
}
|
||||
onValueReorder={onReferencesReorder}
|
||||
loading={loading}
|
||||
/>
|
||||
</ExtendedAttributeRow>
|
||||
) : attribute.data.inputType ===
|
||||
AttributeInputTypeEnum.FILE ? (
|
||||
<BasicAttributeRow label={attribute.label}>
|
||||
<FileUploadField
|
||||
className={classes.fileField}
|
||||
disabled={disabled}
|
||||
loading={loading}
|
||||
file={getFileChoice(attribute)}
|
||||
onFileUpload={file =>
|
||||
onFileChange(attribute.id, file)
|
||||
}
|
||||
onFileUpload={file => onFileChange(attribute.id, file)}
|
||||
onFileDelete={() =>
|
||||
onFileChange(attribute.id, undefined)
|
||||
}
|
||||
|
@ -282,8 +305,10 @@ const Attributes: React.FC<AttributesProps> = ({
|
|||
name: `attribute:${attribute.label}`
|
||||
}}
|
||||
/>
|
||||
</BasicAttributeRow>
|
||||
) : attribute.data.inputType ===
|
||||
AttributeInputTypeEnum.DROPDOWN ? (
|
||||
<BasicAttributeRow label={attribute.label}>
|
||||
<SingleAutocompleteSelectField
|
||||
choices={getSingleChoices(attribute.data.values)}
|
||||
disabled={disabled}
|
||||
|
@ -305,16 +330,16 @@ const Attributes: React.FC<AttributesProps> = ({
|
|||
}
|
||||
allowCustomValues={!attribute.data.isRequired}
|
||||
/>
|
||||
</BasicAttributeRow>
|
||||
) : (
|
||||
<BasicAttributeRow label={attribute.label}>
|
||||
<MultiAutocompleteSelectField
|
||||
choices={getMultiChoices(attribute.data.values)}
|
||||
displayValues={getMultiDisplayValue(attribute)}
|
||||
disabled={disabled}
|
||||
error={!!error}
|
||||
helperText={getErrorMessage(error, intl)}
|
||||
label={intl.formatMessage(
|
||||
messages.multipleValueLable
|
||||
)}
|
||||
label={intl.formatMessage(messages.multipleValueLable)}
|
||||
name={`attribute:${attribute.label}`}
|
||||
value={attribute.value}
|
||||
onChange={event =>
|
||||
|
@ -322,9 +347,8 @@ const Attributes: React.FC<AttributesProps> = ({
|
|||
}
|
||||
allowCustomValues={!attribute.data.isRequired}
|
||||
/>
|
||||
</BasicAttributeRow>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
|
44
src/components/Attributes/BasicAttributeRow.tsx
Normal file
44
src/components/Attributes/BasicAttributeRow.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
attributeSection: {
|
||||
"&:last-of-type": {
|
||||
paddingBottom: 0
|
||||
},
|
||||
padding: theme.spacing(2, 0)
|
||||
},
|
||||
attributeSectionLabel: {
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
}
|
||||
}),
|
||||
{ name: "BasicAttributeRow" }
|
||||
);
|
||||
|
||||
interface BasicAttributeRowProps {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const BasicAttributeRow: React.FC<BasicAttributeRowProps> = props => {
|
||||
const { label, children } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<Grid className={classes.attributeSection} variant="uniform">
|
||||
<div
|
||||
className={classes.attributeSectionLabel}
|
||||
data-test="attribute-label"
|
||||
>
|
||||
<Typography>{label}</Typography>
|
||||
</div>
|
||||
<div data-test="attribute-value">{children}</div>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
BasicAttributeRow.displayName = "BasicAttributeRow";
|
||||
export default BasicAttributeRow;
|
65
src/components/Attributes/ExtendedAttributeRow.tsx
Normal file
65
src/components/Attributes/ExtendedAttributeRow.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
attributeSection: {
|
||||
"&:last-of-type": {
|
||||
paddingBottom: 0
|
||||
},
|
||||
padding: theme.spacing(2, 0)
|
||||
},
|
||||
attributeSectionButton: {
|
||||
float: "right"
|
||||
},
|
||||
attributeSectionLabel: {
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
}
|
||||
}),
|
||||
{ name: "ExtendedAttributeRow" }
|
||||
);
|
||||
|
||||
interface ExtendedAttributeRowProps {
|
||||
label: string;
|
||||
selectLabel: string;
|
||||
disabled: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
const ExtendedAttributeRow: React.FC<ExtendedAttributeRowProps> = props => {
|
||||
const { label, selectLabel, disabled, onSelect, children } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid className={classes.attributeSection} variant="uniform">
|
||||
<div
|
||||
className={classes.attributeSectionLabel}
|
||||
data-test="attribute-label"
|
||||
>
|
||||
<Typography>{label}</Typography>
|
||||
</div>
|
||||
<div data-test="attribute-selector">
|
||||
<Button
|
||||
className={classes.attributeSectionButton}
|
||||
disabled={disabled}
|
||||
variant="text"
|
||||
color="primary"
|
||||
data-test="button-attribute-selector"
|
||||
onClick={onSelect}
|
||||
>
|
||||
{selectLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
<div data-test="attribute-value">{children}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ExtendedAttributeRow.displayName = "ExtendedAttributeRow";
|
||||
export default ExtendedAttributeRow;
|
|
@ -79,15 +79,49 @@ const FILE_ATTRIBUTE: AttributeInput = {
|
|||
}
|
||||
]
|
||||
},
|
||||
id: "ifudbgidfsb",
|
||||
id: "fguygygugyu",
|
||||
label: "File Attribute",
|
||||
value: []
|
||||
};
|
||||
|
||||
const REFERENCE_ATTRIBUTE: AttributeInput = {
|
||||
data: {
|
||||
inputType: AttributeInputTypeEnum.REFERENCE,
|
||||
isRequired: true,
|
||||
values: [
|
||||
{
|
||||
__typename: "AttributeValue",
|
||||
file: null,
|
||||
id: "vbnhgcvjhbvhj",
|
||||
name: "References First Value",
|
||||
slug: "references-first-value"
|
||||
},
|
||||
{
|
||||
__typename: "AttributeValue",
|
||||
file: null,
|
||||
id: "gucngdfdfvdvd",
|
||||
name: "References Second Value",
|
||||
slug: "references-second-value"
|
||||
},
|
||||
{
|
||||
__typename: "AttributeValue",
|
||||
file: null,
|
||||
id: "dfdfdsfdsfdse",
|
||||
name: "References Third Value",
|
||||
slug: "references-third-value"
|
||||
}
|
||||
]
|
||||
},
|
||||
id: "kclsmcdsmcs",
|
||||
label: "References Attribute",
|
||||
value: []
|
||||
};
|
||||
|
||||
export const ATTRIBUTES: AttributeInput[] = [
|
||||
DROPDOWN_ATTRIBUTE,
|
||||
MULTISELECT_ATTRIBUTE,
|
||||
FILE_ATTRIBUTE
|
||||
FILE_ATTRIBUTE,
|
||||
REFERENCE_ATTRIBUTE
|
||||
];
|
||||
|
||||
export const ATTRIBUTES_SELECTED: AttributeInput[] = [
|
||||
|
@ -105,5 +139,13 @@ export const ATTRIBUTES_SELECTED: AttributeInput[] = [
|
|||
{
|
||||
...FILE_ATTRIBUTE,
|
||||
value: [FILE_ATTRIBUTE.data.values[0].slug]
|
||||
},
|
||||
{
|
||||
...REFERENCE_ATTRIBUTE,
|
||||
value: [
|
||||
REFERENCE_ATTRIBUTE.data.values[0].slug,
|
||||
REFERENCE_ATTRIBUTE.data.values[1].slug,
|
||||
REFERENCE_ATTRIBUTE.data.values[2].slug
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import Chip, { ChipProps } from "@saleor/components/Chip";
|
||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import CardDecorator from "../../CardDecorator";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const props: ChipProps = {
|
||||
label: "Lorem Ipsum"
|
||||
};
|
29
src/components/SortableChip/SortableChip.stories.tsx
Normal file
29
src/components/SortableChip/SortableChip.stories.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import SortableChip, {
|
||||
SortableChipProps
|
||||
} from "@saleor/components/SortableChip";
|
||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { SortableContainer } from "react-sortable-hoc";
|
||||
|
||||
const Container = SortableContainer(props => props.children);
|
||||
|
||||
const props: SortableChipProps = {
|
||||
index: 0,
|
||||
label: "Lorem Ipsum"
|
||||
};
|
||||
|
||||
storiesOf("Generics / Sortable chip", module)
|
||||
.addDecorator(CardDecorator)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => (
|
||||
<Container>
|
||||
<SortableChip {...props} />
|
||||
</Container>
|
||||
))
|
||||
.add("with x", () => (
|
||||
<Container>
|
||||
<SortableChip {...props} onClose={() => undefined} />
|
||||
</Container>
|
||||
));
|
68
src/components/SortableChip/SortableChip.tsx
Normal file
68
src/components/SortableChip/SortableChip.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { SortableElement, SortableElementProps } from "react-sortable-hoc";
|
||||
|
||||
import SortableHandle from "./SortableHandle";
|
||||
|
||||
export interface SortableChipProps extends SortableElementProps {
|
||||
className?: string;
|
||||
label: React.ReactNode;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
closeIcon: {
|
||||
cursor: "pointer",
|
||||
fontSize: 16,
|
||||
marginLeft: theme.spacing(),
|
||||
verticalAlign: "middle"
|
||||
},
|
||||
content: {
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
},
|
||||
root: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 18,
|
||||
display: "inline-block",
|
||||
marginRight: theme.spacing(2),
|
||||
padding: "6px 12px"
|
||||
},
|
||||
sortableHandle: {
|
||||
marginRight: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{ name: "SortableChip" }
|
||||
);
|
||||
|
||||
const SortableChip = SortableElement<SortableChipProps>(props => {
|
||||
const { className, label, onClose } = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<div className={classNames(classes.root, className)}>
|
||||
<div className={classes.content}>
|
||||
<SortableHandle
|
||||
className={classes.sortableHandle}
|
||||
data-test="button-drag-handle"
|
||||
/>
|
||||
<Typography data-test="chip-label">{label}</Typography>
|
||||
{onClose && (
|
||||
<CloseIcon
|
||||
className={classes.closeIcon}
|
||||
onClick={onClose}
|
||||
data-test="button-close"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SortableChip.displayName = "SortableChip";
|
||||
export default SortableChip;
|
29
src/components/SortableChip/SortableHandle.tsx
Normal file
29
src/components/SortableChip/SortableHandle.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Draggable from "@saleor/icons/Draggable";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { SortableHandle as SortableHandleHoc } from "react-sortable-hoc";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
drag: {
|
||||
cursor: "grab"
|
||||
}
|
||||
},
|
||||
{ name: "SortableHandle" }
|
||||
);
|
||||
|
||||
interface SortableHandle {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const SortableHandle = SortableHandleHoc(props => {
|
||||
const { className, ...restProps } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<Draggable className={classNames(classes.drag, className)} {...restProps} />
|
||||
);
|
||||
});
|
||||
|
||||
export default SortableHandle;
|
2
src/components/SortableChip/index.ts
Normal file
2
src/components/SortableChip/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./SortableChip";
|
||||
export * from "./SortableChip";
|
|
@ -0,0 +1,27 @@
|
|||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import SortableChipsField, {
|
||||
SortableChipsFieldProps
|
||||
} from "./SortableChipsField";
|
||||
|
||||
const props: SortableChipsFieldProps = {
|
||||
onValueDelete: () => undefined,
|
||||
onValueReorder: () => undefined,
|
||||
values: [
|
||||
{ label: "Item 1", value: "item-1" },
|
||||
{ label: "Item 2", value: "item-2" },
|
||||
{ label: "Item 3", value: "item-3" },
|
||||
{ label: "Item 4", value: "item-4" },
|
||||
{ label: "Item 5", value: "item-5" },
|
||||
{ label: "Item 6", value: "item-6" }
|
||||
]
|
||||
};
|
||||
|
||||
storiesOf("Generics / Sortable chips field", module)
|
||||
.addDecorator(CardDecorator)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <SortableChipsField {...props} />)
|
||||
.add("loading", () => <SortableChipsField {...props} loading={true} />);
|
66
src/components/SortableChipsField/SortableChipsField.tsx
Normal file
66
src/components/SortableChipsField/SortableChipsField.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { SortableContainerProps } from "react-sortable-hoc";
|
||||
|
||||
import Skeleton from "../Skeleton";
|
||||
import DraggableChip from "../SortableChip";
|
||||
import SortableContainer from "./SortableContainer";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
chip: {
|
||||
background: "#fff",
|
||||
color: theme.palette.primary.dark,
|
||||
marginBottom: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "SortableChipsField"
|
||||
}
|
||||
);
|
||||
|
||||
interface SortableChipsFieldValueType {
|
||||
label: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export interface SortableChipsFieldProps extends SortableContainerProps {
|
||||
loading?: boolean;
|
||||
values: SortableChipsFieldValueType[];
|
||||
onValueDelete: (id: string) => void;
|
||||
onValueReorder: ReorderAction;
|
||||
}
|
||||
|
||||
const SortableChipsField: React.FC<SortableChipsFieldProps> = props => {
|
||||
const { loading, values, onValueDelete, onValueReorder } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<SortableContainer
|
||||
axis="xy"
|
||||
lockAxis="xy"
|
||||
useDragHandle
|
||||
onSortEnd={onValueReorder}
|
||||
>
|
||||
<div>
|
||||
{loading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
values.map((value, valueIndex) => (
|
||||
<DraggableChip
|
||||
className={classes.chip}
|
||||
key={valueIndex}
|
||||
index={valueIndex}
|
||||
label={value.label}
|
||||
onClose={() => onValueDelete(value.value)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</SortableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
SortableChipsField.displayName = "SortableChipsField";
|
||||
export default SortableChipsField;
|
5
src/components/SortableChipsField/SortableContainer.tsx
Normal file
5
src/components/SortableChipsField/SortableContainer.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { SortableContainer as SortableContainerHoc } from "react-sortable-hoc";
|
||||
|
||||
const SortableContainer = SortableContainerHoc(({ children }) => children);
|
||||
|
||||
export default SortableContainer;
|
2
src/components/SortableChipsField/index.ts
Normal file
2
src/components/SortableChipsField/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./SortableChipsField";
|
||||
export * from "./SortableChipsField";
|
File diff suppressed because it is too large
Load diff
|
@ -16,7 +16,6 @@ function loadStories() {
|
|||
require("./stories/components/AutocompleteSelectMenu");
|
||||
require("./stories/components/CardMenu");
|
||||
require("./stories/components/Checkbox");
|
||||
require("./stories/components/Chip");
|
||||
require("./stories/components/ColumnPicker");
|
||||
require("./stories/components/Date");
|
||||
require("./stories/components/DateTime");
|
||||
|
|
|
@ -70,6 +70,10 @@ export enum AppTypeEnum {
|
|||
THIRDPARTY = "THIRDPARTY",
|
||||
}
|
||||
|
||||
export enum AttributeEntityTypeEnum {
|
||||
PAGE = "PAGE",
|
||||
}
|
||||
|
||||
export enum AttributeErrorCode {
|
||||
ALREADY_EXISTS = "ALREADY_EXISTS",
|
||||
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||
|
@ -83,6 +87,7 @@ export enum AttributeInputTypeEnum {
|
|||
DROPDOWN = "DROPDOWN",
|
||||
FILE = "FILE",
|
||||
MULTISELECT = "MULTISELECT",
|
||||
REFERENCE = "REFERENCE",
|
||||
}
|
||||
|
||||
export enum AttributeSortField {
|
||||
|
@ -1022,6 +1027,7 @@ export interface AppTokenInput {
|
|||
|
||||
export interface AttributeCreateInput {
|
||||
inputType?: AttributeInputTypeEnum | null;
|
||||
entityType?: AttributeEntityTypeEnum | null;
|
||||
name: string;
|
||||
slug?: string | null;
|
||||
type: AttributeTypeEnum;
|
||||
|
@ -1084,6 +1090,7 @@ export interface AttributeValueInput {
|
|||
values?: (string | null)[] | null;
|
||||
file?: string | null;
|
||||
contentType?: string | null;
|
||||
references?: string[] | null;
|
||||
}
|
||||
|
||||
export interface BulkAttributeValueInput {
|
||||
|
|
Loading…
Reference in a new issue