Fix types

This commit is contained in:
dominik-zeglen 2019-08-09 15:13:50 +02:00
parent 24cd398146
commit f427edf550
15 changed files with 7 additions and 824 deletions

View file

@ -19,9 +19,9 @@ import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { RouteComponentProps, withRouter } from "react-router"; import { RouteComponentProps, withRouter } from "react-router";
import saleorDarkLogoSmall from "@assets/logo-dark-small.svg"; import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
import saleorDarkLogo from "@assets/logo-dark.svg"; import saleorDarkLogo from "@assets/images/logo-dark.svg";
import menuArrowIcon from "@assets/menu-arrow-icon.svg"; import menuArrowIcon from "@assets/images/menu-arrow-icon.svg";
import AppProgressProvider from "@saleor/components/AppProgress"; import AppProgressProvider from "@saleor/components/AppProgress";
import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";

View file

@ -1,62 +0,0 @@
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import {
createStyles,
Theme,
withStyles,
WithStyles
} from "@material-ui/core/styles";
import React from "react";
import NavigationPrompt from "react-router-navigation-prompt";
import i18n from "../i18n";
import { FormContext } from "./Form";
const styles = (theme: Theme) =>
createStyles({
deleteButton: {
"&:hover": {
backgroundColor: theme.palette.error.main
},
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText
}
});
export const ConfirmFormLeaveDialog = withStyles(styles, {
name: "ConfirmFormLeaveDialog"
})(({ classes }: WithStyles<typeof styles>) => (
<FormContext.Consumer>
{({ hasChanged: hasFormChanged }) => (
<NavigationPrompt renderIfNotActive={true} when={hasFormChanged}>
{({ isActive, onCancel, onConfirm }) => (
<Dialog onClose={onCancel} open={isActive}>
<DialogTitle>{i18n.t("Unsaved changes")}</DialogTitle>
<DialogContent>
<DialogContentText>
{i18n.t(
"If you leave this page, unsaved changes will be lost. Are you sure you want to leave?",
{
context: "form leave confirmation"
}
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>
{i18n.t("Cancel", { context: "button" })}
</Button>
<Button onClick={onConfirm} className={classes.deleteButton}>
{i18n.t("Leave page", { context: "button" })}
</Button>
</DialogActions>
</Dialog>
)}
</NavigationPrompt>
)}
</FormContext.Consumer>
));

View file

@ -1,40 +0,0 @@
import Button from "@material-ui/core/Button";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
import Toolbar from "@material-ui/core/Toolbar";
import React from "react";
import i18n from "../../i18n";
const styles = createStyles({
cardActions: {
flexDirection: "row-reverse" as "row-reverse"
}
});
interface FormActionsProps extends WithStyles<typeof styles> {
submitLabel: string;
onCancel?();
onSubmit?(event: React.FormEvent<any>);
}
const FormActions = withStyles(styles, { name: "FormActions" })(
({ classes, onCancel, onSubmit, submitLabel }: FormActionsProps) => (
<Toolbar className={classes.cardActions}>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
type="submit"
>
{submitLabel}
</Button>
{onCancel && (
<Button onClick={onCancel}>
{i18n.t("Cancel", { context: "button" })}
</Button>
)}
</Toolbar>
)
);
FormActions.displayName = "FormActions";
export default FormActions;

View file

@ -1,54 +0,0 @@
import React from "react";
import FormComponent, { FormProps } from "./Form";
interface IFormContext {
hasChanged: boolean;
toggle: () => void;
}
export const FormContext = React.createContext<IFormContext>(undefined);
interface FormProviderState {
hasChanged: boolean;
}
export class FormProvider extends React.Component<{}, FormProviderState> {
state: FormProviderState = {
hasChanged: false
};
toggle = () =>
this.setState(prevState => ({
hasChanged: !prevState.hasChanged
}));
render() {
return (
<FormContext.Provider
value={{
hasChanged: this.state.hasChanged,
toggle: this.toggle
}}
>
{this.props.children}
</FormContext.Provider>
);
}
}
export function Form<T>(props: FormProps<T>) {
return (
<FormContext.Consumer>
{({ hasChanged, toggle }) => (
<FormComponent
{...props}
toggleFormChangeState={toggle}
hasChanged={hasChanged}
/>
)}
</FormContext.Consumer>
);
}
export default Form;

View file

@ -1,4 +1,2 @@
export * from "./Form"; export * from "./Form";
export { default } from "./Form"; export { default } from "./Form";
export { default as FormActions } from "./FormActions";
export * from "./FormActions";

View file

@ -1,7 +1,7 @@
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import colors from "@material-ui/core/colors"; import * as colors from "@material-ui/core/colors";
import { import {
createStyles, createStyles,
Theme, Theme,

View file

@ -1,177 +0,0 @@
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import {
createStyles,
Theme,
withStyles,
WithStyles
} from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import React from "react";
import CardTitle from "@saleor/components/CardTitle";
import ControlledSwitch from "@saleor/components/ControlledSwitch";
import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr";
import TextFieldWithChoice from "@saleor/components/TextFieldWithChoice";
import i18n from "../../../i18n";
import { FormErrors } from "../../../types";
import { VoucherDiscountValueType } from "../../../types/globalTypes";
import { FormData } from "../VoucherDetailsPage";
interface VoucherOptionsProps {
data: FormData;
defaultCurrency: string;
disabled: boolean;
errors: FormErrors<
| "discountType"
| "discountValue"
| "endDate"
| "minAmountSpent"
| "startDate"
| "usageLimit"
>;
onChange: (event: React.ChangeEvent<any>) => void;
}
const styles = (theme: Theme) =>
createStyles({
root: {
display: "grid",
gridColumnGap: theme.spacing.unit * 2 + "px",
gridTemplateColumns: "1fr 1fr"
}
});
const VoucherOptions = withStyles(styles, {
name: "VoucherOptions"
})(
({
classes,
data,
defaultCurrency,
disabled,
errors,
onChange
}: VoucherOptionsProps & WithStyles<typeof styles>) => (
<Card>
<CardTitle title={i18n.t("Detailed Information")} />
<CardContent className={classes.root}>
<TextFieldWithChoice
disabled={disabled}
error={!!errors.discountValue}
ChoiceProps={{
label:
data.discountType === VoucherDiscountValueType.FIXED
? defaultCurrency
: "%",
name: "discountType" as keyof FormData,
values: [
{
label: defaultCurrency,
value: VoucherDiscountValueType.FIXED
},
{
label: "%",
value: VoucherDiscountValueType.PERCENTAGE
}
]
}}
helperText={errors.discountValue}
name={"value" as keyof FormData}
onChange={onChange}
label={i18n.t("Discount Value")}
value={data.value}
type="number"
fullWidth
inputProps={{
min: 0
}}
/>
<TextField
disabled={disabled}
error={!!errors.usageLimit}
helperText={errors.usageLimit || i18n.t("Optional")}
name={"usageLimit" as keyof FormData}
value={data.usageLimit}
onChange={onChange}
label={i18n.t("Usage Limit")}
type="number"
inputProps={{
min: 0
}}
fullWidth
/>
</CardContent>
<Hr />
<CardContent>
<Typography variant="subtitle1">
{i18n.t("Discount Specific Information")}
</Typography>
<FormSpacer />
<div className={classes.root}>
<TextField
disabled={disabled}
error={!!errors.minAmountSpent}
helperText={errors.minAmountSpent || i18n.t("Optional")}
name={"minAmountSpent" as keyof FormData}
value={data.minAmountSpent}
onChange={onChange}
label={i18n.t("Minimum order value")}
fullWidth
/>
</div>
<FormSpacer />
<ControlledSwitch
checked={data.applyOncePerOrder}
label={
<>
{i18n.t("Only once per order", {
context: "voucher application"
})}
<Typography variant="caption">
{i18n.t(
"If this option is disabled, discount will be counted for every eligible product"
)}
</Typography>
</>
}
onChange={onChange}
name={"applyOncePerOrder" as keyof FormData}
disabled={disabled}
/>
</CardContent>
<Hr />
<CardContent>
<Typography variant="subtitle1">{i18n.t("Time Frame")}</Typography>
<FormSpacer />
<div className={classes.root}>
<TextField
disabled={disabled}
error={!!errors.startDate}
helperText={errors.startDate}
name={"startDate" as keyof FormData}
onChange={onChange}
label={i18n.t("Start Date")}
value={data.startDate}
type="date"
fullWidth
/>
<TextField
disabled={disabled}
error={!!errors.endDate}
helperText={errors.endDate}
name={"endDate" as keyof FormData}
onChange={onChange}
label={i18n.t("End Date")}
value={data.endDate}
type="date"
fullWidth
/>
</div>
</CardContent>
</Card>
)
);
export default VoucherOptions;

View file

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

View file

@ -1,68 +0,0 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import AddIcon from "@material-ui/icons/Add";
import React from "react";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import ProductList from "@saleor/components/ProductList";
import { CategoryDetails_category_products_edges_node } from "../../../categories/types/CategoryDetails";
import i18n from "../../../i18n";
import { FilterPageProps, ListActions, PageListProps } from "../../../types";
import { ProductListUrlFilters } from "../../urls";
import ProductListFilter from "../ProductListFilter";
export interface ProductListCardProps
extends PageListProps,
ListActions,
FilterPageProps<ProductListUrlFilters> {
currencySymbol: string;
products: CategoryDetails_category_products_edges_node[];
}
export const ProductListCard: React.StatelessComponent<
ProductListCardProps
> = ({
currencySymbol,
currentTab,
filtersList,
filterTabs,
initialSearch,
onAdd,
onAll,
onSearchChange,
onFilterAdd,
onFilterSave,
onTabChange,
onFilterDelete,
...listProps
}) => (
<Container>
<PageHeader title={i18n.t("Products")}>
<Button onClick={onAdd} color="primary" variant="contained">
{i18n.t("Add product")} <AddIcon />
</Button>
</PageHeader>
<Card>
<ProductListFilter
allTabLabel={i18n.t("All Products")}
currencySymbol={currencySymbol}
currentTab={currentTab}
filterLabel={i18n.t("Select all products where:")}
filterTabs={filterTabs}
filtersList={filtersList}
initialSearch={initialSearch}
searchPlaceholder={i18n.t("Search Products...")}
onAll={onAll}
onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave}
onTabChange={onTabChange}
onFilterDelete={onFilterDelete}
/>
<ProductList {...listProps} />
</Card>
</Container>
);
ProductListCard.displayName = "ProductListCard";
export default ProductListCard;

View file

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

View file

@ -1,366 +0,0 @@
import DialogContentText from "@material-ui/core/DialogContentText";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import React from "react";
import { arrayMove } from "react-sortable-hoc";
import placeholderImg from "@assets/images/placeholder255x255.png";
import ActionDialog from "@saleor/components/ActionDialog";
import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import SearchCategories from "../../containers/SearchCategories";
import SearchCollections from "../../containers/SearchCollections";
import i18n from "../../i18n";
import { decimal, getMutationState, maybe } from "../../misc";
import { productTypeUrl } from "../../productTypes/urls";
import ProductUpdatePage, { FormData } from "../components/ProductUpdatePage";
import ProductUpdateOperations from "../containers/ProductUpdateOperations";
import { TypedProductDetailsQuery } from "../queries";
import {
ProductImageCreate,
ProductImageCreateVariables
} from "../types/ProductImageCreate";
import { ProductVariantBulkDelete } from "../types/ProductVariantBulkDelete";
import {
productImageUrl,
productListUrl,
productUrl,
ProductUrlQueryParams,
productVariantAddUrl,
productVariantEditUrl
} from "../urls";
interface ProductUpdateProps {
id: string;
params: ProductUrlQueryParams;
}
export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
id,
params
}) => {
const navigate = useNavigator();
const notify = useNotifier();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
return (
<SearchCategories variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({ search: searchCategories, result: searchCategoriesOpts }) => (
<SearchCollections variables={DEFAULT_INITIAL_SEARCH_DATA}>
{({ search: searchCollections, result: searchCollectionsOpts }) => (
<TypedProductDetailsQuery
displayLoader
require={["product"]}
variables={{ id }}
>
{({ data, loading, refetch }) => {
const handleDelete = () => {
notify({ text: i18n.t("Product removed") });
navigate(productListUrl());
};
const handleUpdate = () =>
notify({ text: i18n.t("Saved changes") });
const handleImageCreate = (data: ProductImageCreate) => {
const imageError = data.productImageCreate.errors.find(
error =>
error.field ===
("image" as keyof ProductImageCreateVariables)
);
if (imageError) {
notify({
text: imageError.message
});
}
};
const handleImageDeleteSuccess = () =>
notify({
text: i18n.t("Image successfully deleted")
});
const handleVariantAdd = () =>
navigate(productVariantAddUrl(id));
const handleBulkProductVariantDelete = (
data: ProductVariantBulkDelete
) => {
if (data.productVariantBulkDelete.errors.length === 0) {
navigate(productUrl(id), true);
reset();
refetch();
}
};
const product = data ? data.product : undefined;
return (
<ProductUpdateOperations
product={product}
onBulkProductVariantDelete={handleBulkProductVariantDelete}
onDelete={handleDelete}
onImageCreate={handleImageCreate}
onImageDelete={handleImageDeleteSuccess}
onUpdate={handleUpdate}
>
{({
bulkProductVariantDelete,
createProductImage,
deleteProduct,
deleteProductImage,
reorderProductImages,
updateProduct,
updateSimpleProduct
}) => {
const handleImageDelete = (id: string) => () =>
deleteProductImage.mutate({ id });
const handleImageEdit = (imageId: string) => () =>
navigate(productImageUrl(id, imageId));
const handleSubmit = (data: FormData) => {
if (product) {
if (product.productType.hasVariants) {
updateProduct.mutate({
attributes: data.attributes,
basePrice: decimal(data.basePrice),
category: data.category.value,
chargeTaxes: data.chargeTaxes,
collections: data.collections.map(
collection => collection.value
),
descriptionJson: JSON.stringify(data.description),
id: product.id,
isPublished: data.isPublished,
name: data.name,
publicationDate:
data.publicationDate !== ""
? data.publicationDate
: null
});
} else {
updateSimpleProduct.mutate({
attributes: data.attributes,
basePrice: decimal(data.basePrice),
category: data.category.value,
chargeTaxes: data.chargeTaxes,
collections: data.collections.map(
collection => collection.value
),
descriptionJson: JSON.stringify(data.description),
id: product.id,
isPublished: data.isPublished,
name: data.name,
productVariantId: product.variants[0].id,
productVariantInput: {
quantity: data.stockQuantity,
sku: data.sku
},
publicationDate:
data.publicationDate !== ""
? data.publicationDate
: null
});
}
}
};
const disableFormSave =
createProductImage.opts.loading ||
deleteProduct.opts.loading ||
reorderProductImages.opts.loading ||
updateProduct.opts.loading ||
loading;
const formTransitionState = getMutationState(
updateProduct.opts.called ||
updateSimpleProduct.opts.called,
updateProduct.opts.loading ||
updateSimpleProduct.opts.loading,
maybe(
() => updateProduct.opts.data.productUpdate.errors
),
maybe(
() =>
updateSimpleProduct.opts.data.productUpdate.errors
),
maybe(
() =>
updateSimpleProduct.opts.data.productVariantUpdate
.errors
)
);
const deleteTransitionState = getMutationState(
deleteProduct.opts.called,
deleteProduct.opts.loading,
maybe(
() => deleteProduct.opts.data.productDelete.errors
)
);
const bulkProductVariantDeleteTransitionState = getMutationState(
bulkProductVariantDelete.opts.called,
bulkProductVariantDelete.opts.loading,
maybe(
() =>
bulkProductVariantDelete.opts.data
.productVariantBulkDelete.errors
)
);
return (
<>
<WindowTitle title={maybe(() => data.product.name)} />
<ProductUpdatePage
categories={maybe(
() => searchCategoriesOpts.data.categories.edges,
[]
).map(edge => edge.node)}
collections={maybe(
() =>
searchCollectionsOpts.data.collections.edges,
[]
).map(edge => edge.node)}
disabled={disableFormSave}
errors={maybe(
() =>
updateProduct.opts.data.productUpdate.errors,
[]
)}
fetchCategories={searchCategories}
fetchCollections={searchCollections}
saveButtonBarState={formTransitionState}
images={maybe(() => data.product.images)}
header={maybe(() => product.name)}
placeholderImage={placeholderImg}
product={product}
productCollections={maybe(
() => product.collections
)}
variants={maybe(() => product.variants)}
onAttributesEdit={() =>
navigate(
productTypeUrl(data.product.productType.id)
)
}
onBack={() => {
navigate(productListUrl());
}}
onDelete={() =>
navigate(
productUrl(id, {
action: "remove"
})
)
}
onProductShow={() => {
if (product) {
window.open(product.url);
}
}}
onImageReorder={({ newIndex, oldIndex }) => {
if (product) {
let ids = product.images.map(image => image.id);
ids = arrayMove(ids, oldIndex, newIndex);
reorderProductImages.mutate({
imagesIds: ids,
productId: product.id
});
}
}}
onSubmit={handleSubmit}
onVariantAdd={handleVariantAdd}
onVariantShow={variantId => () =>
navigate(
productVariantEditUrl(product.id, variantId)
)}
onImageUpload={file => {
if (product) {
createProductImage.mutate({
alt: "",
image: file,
product: product.id
});
}
}}
onImageEdit={handleImageEdit}
onImageDelete={handleImageDelete}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
productUrl(id, {
action: "remove-variants",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={params.action === "remove"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={deleteTransitionState}
onConfirm={() => deleteProduct.mutate({ id })}
variant="delete"
title={i18n.t("Remove product")}
>
<DialogContentText
dangerouslySetInnerHTML={{
__html: i18n.t(
"Are you sure you want to remove <strong>{{ name }}</strong>?",
{
name: product ? product.name : undefined
}
)
}}
/>
</ActionDialog>
<ActionDialog
open={params.action === "remove-variants"}
onClose={() => navigate(productUrl(id), true)}
confirmButtonState={
bulkProductVariantDeleteTransitionState
}
onConfirm={() =>
bulkProductVariantDelete.mutate({
ids: params.ids
})
}
variant="delete"
title={i18n.t("Remove product variants")}
>
<DialogContentText
dangerouslySetInnerHTML={{
__html: i18n.t(
"Are you sure you want to remove <strong>{{ number }}</strong> variants?",
{
number: maybe(
() => params.ids.length.toString(),
"..."
)
}
)
}}
/>
</ActionDialog>
</>
);
}}
</ProductUpdateOperations>
);
}}
</TypedProductDetailsQuery>
)}
</SearchCollections>
)}
</SearchCategories>
);
};
export default ProductUpdate;

View file

@ -3,6 +3,7 @@ import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import React from "react"; import React from "react";
import placeholderImg from "@assets/images/placeholder255x255.png";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
@ -32,7 +33,6 @@ import {
productVariantAddUrl, productVariantAddUrl,
productVariantEditUrl productVariantEditUrl
} from "../../urls"; } from "../../urls";
import placeholderImg from "../@assets/images/placeholder255x255.png";
import { import {
createImageReorderHandler, createImageReorderHandler,
createImageUploadHandler, createImageUploadHandler,

View file

@ -11,13 +11,12 @@ import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import photoIcon from "@assets/images/photo-icon.svg";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import i18n from "../../../i18n"; import i18n from "../../../i18n";
import { getUserInitials, maybe } from "../../../misc"; import { getUserInitials, maybe } from "../../../misc";
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails"; import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
import photoIcon from "../@assets/images/photo-icon.svg";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
avatar: { avatar: {

View file

@ -27,7 +27,7 @@ jest.mock("draft-js/lib/generateRandomKey");
(generateRandomKey as any).mockImplementation(() => "testKey"); (generateRandomKey as any).mockImplementation(() => "testKey");
initStoryshots({ initStoryshots({
configPath: "saleor/static/dashboard-next/storybook/", configPath: "src/storybook/",
test({ story }) { test({ story }) {
const result = render(story.render() as any); const result = render(story.render() as any);
expect(toJSON(result)).toMatchSnapshot(); expect(toJSON(result)).toMatchSnapshot();

View file

@ -1,43 +0,0 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import placeholderImage from "@assets/images/placeholder255x255.png";
import { category as categoryFixture } from "../../../categories/fixtures";
import {
filterPageProps,
filters,
listActionsProps,
pageListProps
} from "../../../fixtures";
import ProductListCard, {
ProductListCardProps
} from "../../../products/components/ProductListCard";
import Decorator from "../../Decorator";
const products = categoryFixture(placeholderImage).products.edges.map(
edge => edge.node
);
const props: ProductListCardProps = {
...listActionsProps,
...pageListProps.default,
...filterPageProps,
products
};
storiesOf("Views / Products / Product list", module)
.addDecorator(Decorator)
.add("default", () => <ProductListCard {...props} />)
.add("with custom filters", () => (
<ProductListCard {...props} filtersList={filters} />
))
.add("loading", () => (
<ProductListCard
{...props}
products={undefined}
filtersList={undefined}
currentTab={undefined}
disabled={true}
/>
))
.add("no data", () => <ProductListCard {...props} products={[]} />);