saleor-dashboard/src/components/Attributes/Attributes.tsx
Piotr Grundas 2a21609eae
Numeric attributes (#1065)
* Update schema, types

* Add numeric unit control

* Improvements, tests

* Cleanup

* Add messages

* Small fixes

* Add test id's

* Improve useForm, logic

* Use short names

* Review corrections

* Small improvements
2021-04-29 10:58:03 +02:00

177 lines
5.2 KiB
TypeScript

import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import IconButton from "@material-ui/core/IconButton";
import Typography from "@material-ui/core/Typography";
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import { AttributeReference } from "@saleor/attributes/utils/data";
import CardTitle from "@saleor/components/CardTitle";
import Hr from "@saleor/components/Hr";
import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFragment";
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { FormsetAtomicData } from "@saleor/hooks/useFormset";
import { makeStyles } from "@saleor/theme";
import {
AttributeEntityTypeEnum,
AttributeInputTypeEnum,
MeasurementUnitsEnum
} from "@saleor/types/globalTypes";
import classNames from "classnames";
import React from "react";
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
import AttributeRow, { AttributeRowHandlers } from "./AttributeRow";
import { VariantAttributeScope } from "./types";
export interface AttributeInputData {
inputType: AttributeInputTypeEnum;
entityType?: AttributeEntityTypeEnum;
unit?: MeasurementUnitsEnum | null;
variantAttributeScope?: VariantAttributeScope;
isRequired: boolean;
values: AttributeValueFragment[];
selectedValues?: AttributeValueFragment[];
references?: AttributeReference[];
}
export type AttributeInput = FormsetAtomicData<AttributeInputData, string[]>;
export type AttributeFileInput = FormsetAtomicData<AttributeInputData, File[]>;
export interface AttributesProps extends AttributeRowHandlers {
attributes: AttributeInput[];
disabled: boolean;
loading: boolean;
errors: Array<
ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment
>;
title?: React.ReactNode;
}
const useStyles = makeStyles(
theme => ({
attributeSection: {
"&:last-of-type": {
paddingBottom: 0
},
padding: theme.spacing(2, 0)
},
attributeSectionLabel: {
alignItems: "center",
display: "flex"
},
card: {
overflow: "visible"
},
cardContent: {
"&:last-child": {
paddingBottom: theme.spacing(1)
},
paddingTop: theme.spacing(1)
},
expansionBar: {
display: "flex"
},
expansionBarButton: {
marginBottom: theme.spacing(1)
},
expansionBarButtonIcon: {
transition: theme.transitions.duration.short + "ms"
},
expansionBarLabel: {
color: theme.palette.text.disabled,
fontSize: 14
},
expansionBarLabelContainer: {
alignItems: "center",
display: "flex",
flex: 1
},
rotate: {
transform: "rotate(180deg)"
},
uploadFileButton: {
float: "right"
},
uploadFileContent: {
color: theme.palette.primary.main,
float: "right",
fontSize: "1rem"
}
}),
{ name: "Attributes" }
);
const messages = defineMessages({
attributesNumber: {
defaultMessage: "{number} Attributes",
description: "number of attributes"
},
header: {
defaultMessage: "Attributes",
description: "attributes, section header"
}
});
const Attributes: React.FC<AttributesProps> = ({
attributes,
errors,
title,
...props
}) => {
const intl = useIntl();
const classes = useStyles({});
const [expanded, setExpansionStatus] = React.useState(true);
const toggleExpansion = () => setExpansionStatus(!expanded);
return (
<Card className={classes.card}>
<CardTitle title={title || intl.formatMessage(messages.header)} />
<CardContent className={classes.cardContent}>
<div className={classes.expansionBar}>
<div className={classes.expansionBarLabelContainer}>
<Typography className={classes.expansionBarLabel} variant="caption">
<FormattedMessage
{...messages.attributesNumber}
values={{
number: attributes.length
}}
/>
</Typography>
</div>
<IconButton
className={classes.expansionBarButton}
onClick={toggleExpansion}
data-test="attributes-expand"
>
<ArrowDropDownIcon
className={classNames(classes.expansionBarButtonIcon, {
[classes.rotate]: expanded
})}
/>
</IconButton>
</div>
{expanded && attributes.length > 0 && (
<>
<Hr />
{attributes.map((attribute, attributeIndex) => {
const error = errors.find(err =>
err.attributes?.includes(attribute.id)
);
return (
<React.Fragment key={attribute.id}>
{attributeIndex > 0 && <Hr />}
<AttributeRow
attribute={attribute}
error={error}
{...props}
/>
</React.Fragment>
);
})}
</>
)}
</CardContent>
</Card>
);
};
Attributes.displayName = "Attributes";
export default Attributes;