Add drag-and-drop to allow variants reordering

This commit is contained in:
Dawid Tarasiuk 2020-09-17 13:31:09 +02:00
parent 45d33c3920
commit 88b8950408
13 changed files with 98 additions and 55 deletions

View file

@ -15,6 +15,7 @@ interface ImageUploadProps {
isActiveClassName?: string;
iconContainerClassName?: string;
iconContainerActiveClassName?: string;
hideUploadIcon?: boolean;
onImageUpload: (file: FileList) => void;
}
@ -66,6 +67,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
iconContainerActiveClassName,
iconContainerClassName,
isActiveClassName,
hideUploadIcon,
onImageUpload
} = props;
@ -82,6 +84,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
[isActiveClassName]: isDragActive
})}
>
{!hideUploadIcon && (
<div
className={classNames(iconContainerClassName, {
[iconContainerActiveClassName]: isDragActive
@ -100,6 +103,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
/>
</Typography>
</div>
)}
</div>
{children && children({ isDragActive })}
</>

View file

@ -83,9 +83,6 @@ const useStyles = makeStyles(
imageUploadActive: {
zIndex: 1
},
imageUploadIcon: {
display: "none"
},
imageUploadIconActive: {
display: "block"
},
@ -253,7 +250,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
className={classes.imageUpload}
isActiveClassName={classes.imageUploadActive}
disableClick={true}
iconContainerClassName={classes.imageUploadIcon}
hideUploadIcon={true}
iconContainerActiveClassName={classes.imageUploadIconActive}
onImageUpload={handleImageUpload}
>

View file

@ -18,7 +18,7 @@ import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { FetchMoreProps, ListActions } from "@saleor/types";
import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
@ -73,6 +73,7 @@ export interface ProductUpdatePageProps extends ListActions {
fetchCollections: (query: string) => void;
onVariantsAdd: () => void;
onVariantShow: (id: string) => () => void;
onVariantReorder: ReorderAction;
onImageDelete: (id: string) => () => void;
onBack?();
onDelete();
@ -120,6 +121,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onVariantAdd,
onVariantsAdd,
onVariantShow,
onVariantReorder,
isChecked,
selected,
toggle,
@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onRowClick={onVariantShow}
onVariantAdd={onVariantAdd}
onVariantsAdd={onVariantsAdd}
onVariantReorder={onVariantReorder}
toolbar={toolbar}
isChecked={isChecked}
selected={selected}

View file

@ -14,6 +14,7 @@ import useFormset, {
} from "@saleor/hooks/useFormset";
import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { ReorderAction } from "@saleor/types";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import React from "react";
import { useIntl } from "react-intl";
@ -56,6 +57,7 @@ interface ProductVariantCreatePageProps {
onBack: () => void;
onSubmit: (data: ProductVariantCreatePageSubmitData) => void;
onVariantClick: (variantId: string) => void;
onVariantReorder: ReorderAction;
}
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
weightUnit,
onBack,
onSubmit,
onVariantClick
onVariantClick,
onVariantReorder
}) => {
const intl = useIntl();
const attributeInput = React.useMemo(
@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
return onVariantClick(variantId);
}
}}
onReorder={onVariantReorder}
/>
</div>
<div>

View file

@ -1,13 +1,17 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import { makeStyles } from "@material-ui/core/styles";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import CardTitle from "@saleor/components/CardTitle";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import {
SortableTableBody,
SortableTableRow
} from "@saleor/components/SortableTable";
import TableCellAvatar from "@saleor/components/TableCellAvatar";
import { ReorderAction } from "@saleor/types";
import classNames from "classnames";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
@ -26,6 +30,7 @@ const useStyles = makeStyles(
cursor: "pointer"
},
tabActive: {
"& > td:first-child": {
"&:before": {
background: theme.palette.primary.main,
content: '""',
@ -37,6 +42,7 @@ const useStyles = makeStyles(
},
position: "relative"
}
}
}),
{ name: "ProductVariantNavigation" }
);
@ -49,10 +55,18 @@ interface ProductVariantNavigationProps {
| ProductVariantCreateData_product_variants[];
onAdd?: () => void;
onRowClick: (variantId: string) => void;
onReorder: ReorderAction;
}
const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props => {
const { current, fallbackThumbnail, variants, onAdd, onRowClick } = props;
const {
current,
fallbackThumbnail,
variants,
onAdd,
onRowClick,
onReorder
} = props;
const classes = useStyles(props);
const intl = useIntl();
@ -66,18 +80,18 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
})}
/>
<ResponsiveTable>
<TableBody>
{renderCollection(variants, variant => (
<TableRow
<SortableTableBody onSortEnd={onReorder}>
{renderCollection(variants, (variant, variantIndex) => (
<SortableTableRow
hover={!!variant}
key={variant ? variant.id : "skeleton"}
className={classes.link}
index={variantIndex}
className={classNames(classes.link, {
[classes.tabActive]: variant && variant.id === current
})}
onClick={variant ? () => onRowClick(variant.id) : undefined}
>
<TableCellAvatar
className={classNames({
[classes.tabActive]: variant && variant.id === current
})}
thumbnail={maybe(
() => variant.images[0].url,
fallbackThumbnail
@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
<TableCell className={classes.colName}>
{variant ? variant.name || variant.sku : <Skeleton />}
</TableCell>
</TableRow>
</SortableTableRow>
))}
{onAdd ? (
<TableRow>
<TableCell colSpan={2}>
<TableCell colSpan={3}>
<Button color="primary" onClick={onAdd}>
<FormattedMessage
defaultMessage="Add variant"
@ -110,7 +124,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
</TableCell>
</TableRow>
)}
</TableBody>
</SortableTableBody>
</ResponsiveTable>
</Card>
);

View file

@ -19,6 +19,7 @@ import {
getAttributeInputFromVariant,
getStockInputFromVariant
} from "@saleor/products/utils/data";
import { ReorderAction } from "@saleor/types";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { diff } from "fast-array-diff";
@ -60,6 +61,7 @@ interface ProductVariantPageProps {
placeholderImage?: string;
header: string;
warehouses: WarehouseFragment[];
onVariantReorder: ReorderAction;
onAdd();
onBack();
onDelete();
@ -82,7 +84,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onDelete,
onImageSelect,
onSubmit,
onVariantClick
onVariantClick,
onVariantReorder
}) => {
const attributeInput = React.useMemo(
() => getAttributeInputFromVariant(variant),
@ -188,6 +191,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
return onVariantClick(variantId);
}
}}
onReorder={onVariantReorder}
/>
</div>
<div>

View file

@ -3,9 +3,7 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Hidden from "@material-ui/core/Hidden";
import { makeStyles } from "@material-ui/core/styles";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import CardTitle from "@saleor/components/CardTitle";
import Checkbox from "@saleor/components/Checkbox";
@ -14,13 +12,17 @@ import Money from "@saleor/components/Money";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import Skeleton from "@saleor/components/Skeleton";
import {
SortableTableBody,
SortableTableRow
} from "@saleor/components/SortableTable";
import TableHead from "@saleor/components/TableHead";
import { ProductVariant_costPrice } from "@saleor/fragments/types/ProductVariant";
import React from "react";
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
import { maybe, renderCollection } from "../../../misc";
import { ListActions } from "../../../types";
import { ListActions, ReorderAction } from "../../../types";
import {
ProductDetails_product_variants,
ProductDetails_product_variants_stocks_warehouse
@ -171,12 +173,13 @@ interface ProductVariantsProps extends ListActions {
disabled: boolean;
variants: ProductDetails_product_variants[];
fallbackPrice?: ProductVariant_costPrice;
onVariantReorder: ReorderAction;
onRowClick: (id: string) => () => void;
onVariantAdd?();
onVariantsAdd?();
}
const numberOfColumns = 5;
const numberOfColumns = 6;
export const ProductVariants: React.FC<ProductVariantsProps> = props => {
const {
@ -186,6 +189,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
onRowClick,
onVariantAdd,
onVariantsAdd,
onVariantReorder,
isChecked,
selected,
toggle,
@ -266,6 +270,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
items={variants}
toggleAll={toggleAll}
toolbar={toolbar}
dragRows
>
<TableCell className={classes.colName}>
<FormattedMessage
@ -291,8 +296,8 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
/>
</TableCell>
</TableHead>
<TableBody>
{renderCollection(variants, variant => {
<SortableTableBody onSortEnd={onVariantReorder}>
{renderCollection(variants, (variant, variantIndex) => {
const isSelected = variant ? isChecked(variant.id) : false;
const numAvailable =
variant && variant.stocks
@ -303,11 +308,12 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
: null;
return (
<TableRow
<SortableTableRow
selected={isSelected}
hover={!!variant}
onClick={onRowClick(variant.id)}
key={variant ? variant.id : "skeleton"}
index={variantIndex || 0}
className={classes.link}
>
<TableCell padding="checkbox">
@ -354,10 +360,10 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
)
)}
</TableCell>
</TableRow>
</SortableTableRow>
);
})}
</TableBody>
</SortableTableBody>
</ResponsiveTable>
)}
</Card>

View file

@ -289,6 +289,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
onVariantShow={variantId => () =>
navigate(productVariantEditUrl(product.id, variantId))}
onVariantReorder={() => undefined} // TODO: ...
onImageUpload={handleImageUpload}
onImageEdit={handleImageEdit}
onImageDelete={handleImageDelete}

View file

@ -202,6 +202,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
onVariantClick={variantId => {
navigate(productVariantEditUrl(productId, variantId));
}}
onVariantReorder={() => undefined} // TODO: ...
/>
<ProductVariantDeleteDialog
confirmButtonState={deleteVariantOpts.status}

View file

@ -126,6 +126,7 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
onBack={handleBack}
onSubmit={handleSubmit}
onVariantClick={handleVariantClick}
onVariantReorder={() => undefined} // TODO: ...
saveButtonBarState={variantCreateResult.status}
warehouses={
warehouses.data?.warehouses.edges.map(edge => edge.node) || []

View file

@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = {
onImageUpload: () => undefined,
onSubmit: () => undefined,
onVariantAdd: () => undefined,
onVariantReorder: () => undefined,
onVariantShow: () => undefined,
onVariantsAdd: () => undefined,
placeholderImage,

View file

@ -23,6 +23,7 @@ storiesOf("Views / Products / Create product variant", module)
onBack={() => undefined}
onSubmit={() => undefined}
onVariantClick={undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>
@ -54,6 +55,7 @@ storiesOf("Views / Products / Create product variant", module)
onBack={() => undefined}
onSubmit={() => undefined}
onVariantClick={undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>
@ -69,6 +71,7 @@ storiesOf("Views / Products / Create product variant", module)
onBack={() => undefined}
onSubmit={() => undefined}
onVariantClick={undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>
@ -87,6 +90,7 @@ storiesOf("Views / Products / Create product variant", module)
onBack={() => undefined}
onSubmit={() => undefined}
onVariantClick={undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>

View file

@ -24,6 +24,7 @@ storiesOf("Views / Products / Product variant details", module)
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>
@ -41,6 +42,7 @@ storiesOf("Views / Products / Product variant details", module)
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
warehouses={warehouseList}
/>
@ -56,6 +58,7 @@ storiesOf("Views / Products / Product variant details", module)
onImageSelect={() => undefined}
onSubmit={() => undefined}
onVariantClick={() => undefined}
onVariantReorder={() => undefined}
saveButtonBarState="default"
errors={[
{