Product variants enhancements (#1918)

* Remove savebar docking

* Hide attributes if empty

* Select text in autocomplete after initial click

* Update snapshots

* Use stable macaw version

* Remove maybes

* Add util filter function
This commit is contained in:
Dominik Żegleń 2022-03-16 10:45:15 +01:00 committed by GitHub
parent 3e96a68a6e
commit ac4a219023
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 191 additions and 291 deletions

5
package-lock.json generated
View file

@ -4828,8 +4828,9 @@
} }
}, },
"@saleor/macaw-ui": { "@saleor/macaw-ui": {
"version": "github:saleor/macaw-ui#1f78f97748c00a64ca46973c32eacc4d9a1ac2ac", "version": "0.3.3",
"from": "github:saleor/macaw-ui#SALEOR-5840-button-states", "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.3.3.tgz",
"integrity": "sha512-b2dLlOAXDe2OSmYeZSFKwiRMrJc+YkGhvvQCHQXHHP89TZbGZlWP0F13ycCI3OJSfYbCWZlQGM+alMiE31HOuA==",
"requires": { "requires": {
"clsx": "^1.1.1", "clsx": "^1.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View file

@ -28,7 +28,7 @@
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/lab": "^4.0.0-alpha.58",
"@material-ui/styles": "^4.11.4", "@material-ui/styles": "^4.11.4",
"@saleor/macaw-ui": "github:saleor/macaw-ui#SALEOR-5840-button-states", "@saleor/macaw-ui": "^0.3.3",
"@saleor/sdk": "^0.4.2", "@saleor/sdk": "^0.4.2",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6", "@types/faker": "^5.1.6",

View file

@ -13,7 +13,6 @@ import {
} from "@saleor/macaw-ui"; } from "@saleor/macaw-ui";
import { isDarkTheme } from "@saleor/misc"; import { isDarkTheme } from "@saleor/misc";
import { staffMemberDetailsUrl } from "@saleor/staff/urls"; import { staffMemberDetailsUrl } from "@saleor/staff/urls";
import classNames from "classnames";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import useRouter from "use-react-router"; import useRouter from "use-react-router";
@ -41,9 +40,6 @@ const useStyles = makeStyles(
position: "sticky", position: "sticky",
zIndex: 10 zIndex: 10
}, },
appActionDocked: {
position: "static"
},
appLoader: { appLoader: {
height: appLoaderHeight, height: appLoaderHeight,
marginBottom: theme.spacing(4), marginBottom: theme.spacing(4),
@ -123,7 +119,7 @@ interface AppLayoutProps {
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => { const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
const classes = useStyles({}); const classes = useStyles({});
const { themeType, setTheme } = useTheme(); const { themeType, setTheme } = useTheme();
const { anchor: appActionAnchor, docked } = useActionBar(); const { anchor: appActionAnchor } = useActionBar();
const appHeaderAnchor = useBacklink(); const appHeaderAnchor = useBacklink();
const { logout, user } = useUser(); const { logout, user } = useUser();
const navigate = useNavigator(); const navigate = useNavigator();
@ -234,12 +230,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
: children} : children}
</main> </main>
</div> </div>
<div <div className={classes.appAction} ref={appActionAnchor} />
className={classNames(classes.appAction, {
[classes.appActionDocked]: docked
})}
ref={appActionAnchor}
/>
</div> </div>
</div> </div>
</> </>

View file

@ -90,7 +90,8 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
...rest ...rest
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
const anchor = React.useRef<HTMLInputElement | null>(null); const anchor = React.useRef<HTMLDivElement | null>(null);
const input = React.useRef<HTMLInputElement | null>(null);
const handleChange = (item: string) => { const handleChange = (item: string) => {
onChange({ onChange({
@ -188,6 +189,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
if (fetchOnFocus) { if (fetchOnFocus) {
fetchChoices(inputValue); fetchChoices(inputValue);
} }
input.current.select();
} }
}; };
@ -224,6 +226,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
fullWidth={true} fullWidth={true}
onBlur={onBlur} onBlur={onBlur}
ref={anchor} ref={anchor}
inputRef={input}
/> />
{isOpen && (!!inputValue || !!choices.length) && ( {isOpen && (!!inputValue || !!choices.length) && (
<Popper <Popper

View file

@ -29,7 +29,6 @@ import { FetchMoreProps, RelayToFlat, ReorderAction } from "@saleor/types";
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { defineMessages, useIntl } from "react-intl";
import { maybe } from "../../../misc";
import ProductShipping from "../ProductShipping/ProductShipping"; import ProductShipping from "../ProductShipping/ProductShipping";
import ProductStocks, { ProductStockInput } from "../ProductStocks"; import ProductStocks, { ProductStockInput } from "../ProductStocks";
import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings"; import ProductVariantCheckoutSettings from "../ProductVariantCheckoutSettings/ProductVariantCheckoutSettings";
@ -73,6 +72,11 @@ export interface ProductVariantPageSubmitData
removeStocks: string[]; removeStocks: string[];
} }
function byAttributeScope(scope: VariantAttributeScope) {
return (attribute: AttributeInput) =>
attribute.data.variantAttributeScope === scope;
}
interface ProductVariantPageProps { interface ProductVariantPageProps {
assignReferencesAttributeId?: string; assignReferencesAttributeId?: string;
defaultVariantId?: string; defaultVariantId?: string;
@ -223,175 +227,184 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
handlers, handlers,
hasChanged, hasChanged,
submit submit
}) => ( }) => {
<> const nonSelectionAttributes = data.attributes.filter(
<Grid variant="inverted"> byAttributeScope(VariantAttributeScope.NOT_VARIANT_SELECTION)
<div> );
<ProductVariantNavigation const selectionAttributes = data.attributes.filter(
current={variant ? variant.id : undefined} byAttributeScope(VariantAttributeScope.VARIANT_SELECTION)
defaultVariantId={defaultVariantId} );
fallbackThumbnail={maybe(
() => variant.product.thumbnail.url
)}
variants={maybe(() => variant.product.variants)}
onAdd={onAdd}
onRowClick={(variantId: string) => {
if (variant) {
return onVariantClick(variantId);
}
}}
onReorder={onVariantReorder}
/>
</div>
<div>
<VariantDetailsChannelsAvailabilityCard variant={variant} />
<Attributes
entityId={variant?.id}
title={intl.formatMessage(messages.nonSelectionAttributes)}
attributes={data.attributes.filter(
attribute =>
attribute.data.variantAttributeScope ===
VariantAttributeScope.NOT_VARIANT_SELECTION
)}
attributeValues={attributeValues}
loading={loading}
disabled={loading}
errors={errors}
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
onAttributeSelectBlur={onAttributeSelectBlur}
/>
<CardSpacer />
<Attributes
entityId={variant?.id}
title={intl.formatMessage(
messages.selectionAttributesHeader
)}
attributes={data.attributes.filter(
attribute =>
attribute.data.variantAttributeScope ===
VariantAttributeScope.VARIANT_SELECTION
)}
attributeValues={attributeValues}
loading={loading}
disabled={loading}
errors={errors}
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
onAttributeSelectBlur={onAttributeSelectBlur}
/>
<CardSpacer />
<ProductVariantMedia
disabled={loading}
media={media}
placeholderImage={placeholderImage}
onImageAdd={toggleModal}
/>
<CardSpacer />
<ProductVariantPrice
disabled={!variant}
ProductVariantChannelListings={data.channelListings.map(
channel => ({
...channel.data,
...channel.value
})
)}
errors={channelErrors}
loading={loading}
onChange={handlers.changeChannels}
/>
<CardSpacer />
<ProductVariantCheckoutSettings
data={data}
disabled={loading}
errors={errors}
onChange={change}
/>
<CardSpacer />
<ProductShipping return (
data={data} <>
disabled={loading} <Grid variant="inverted">
errors={errors} <div>
weightUnit={variant?.weight?.unit || defaultWeightUnit} <ProductVariantNavigation
onChange={change} current={variant?.id}
/> defaultVariantId={defaultVariantId}
<CardSpacer /> fallbackThumbnail={variant?.product.thumbnail.url}
<ProductStocks variants={variant?.product.variants}
productVariantChannelListings={data.channelListings.map( onAdd={onAdd}
channel => ({ onRowClick={(variantId: string) => {
...channel.data, if (variant) {
...channel.value return onVariantClick(variantId);
}) }
}}
onReorder={onVariantReorder}
/>
</div>
<div>
<VariantDetailsChannelsAvailabilityCard variant={variant} />
{nonSelectionAttributes.length > 0 && (
<>
<Attributes
entityId={variant?.id}
title={intl.formatMessage(
messages.nonSelectionAttributes
)}
attributes={nonSelectionAttributes}
attributeValues={attributeValues}
loading={loading}
disabled={loading}
errors={errors}
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
onAttributeSelectBlur={onAttributeSelectBlur}
/>
<CardSpacer />
</>
)} )}
onVariantChannelListingChange={handlers.changeChannels} {selectionAttributes.length > 0 && (
data={data} <>
disabled={loading} <Attributes
hasVariants={true} entityId={variant?.id}
errors={errors} title={intl.formatMessage(
formErrors={formErrors} messages.selectionAttributesHeader
stocks={data.stocks} )}
warehouses={warehouses} attributes={selectionAttributes}
onChange={handlers.changeStock} attributeValues={attributeValues}
onFormDataChange={change} loading={loading}
onChangePreorderEndDate={handlers.changePreorderEndDate} disabled={loading}
onEndPreorderTrigger={ errors={errors}
!!variant?.preorder onChange={handlers.selectAttribute}
? () => setIsEndPreorderModalOpened(true) onMultiChange={handlers.selectAttributeMultiple}
: null onFileChange={handlers.selectAttributeFile}
} onReferencesRemove={handlers.selectAttributeReference}
onWarehouseStockAdd={handlers.addStock} onReferencesAddClick={onAssignReferencesClick}
onWarehouseStockDelete={handlers.deleteStock} onReferencesReorder={handlers.reorderAttributeValue}
onWarehouseConfigure={onWarehouseConfigure} fetchAttributeValues={fetchAttributeValues}
/> fetchMoreAttributeValues={fetchMoreAttributeValues}
<CardSpacer /> onAttributeSelectBlur={onAttributeSelectBlur}
<Metadata data={data} onChange={handlers.changeMetadata} /> />
</div> <CardSpacer />
</Grid> </>
<Savebar )}
disabled={loading || formDisabled || !hasChanged} <ProductVariantMedia
state={saveButtonBarState} disabled={loading}
onCancel={onBack} media={media}
onDelete={onDelete} placeholderImage={placeholderImage}
onSubmit={submit} onImageAdd={toggleModal}
/> />
{canOpenAssignReferencesAttributeDialog && ( <CardSpacer />
<AssignAttributeValueDialog <ProductVariantPrice
attributeValues={getAttributeValuesFromReferences( disabled={!variant}
assignReferencesAttributeId, ProductVariantChannelListings={data.channelListings.map(
data.attributes, channel => ({
referencePages, ...channel.data,
referenceProducts ...channel.value
)} })
hasMore={handlers.fetchMoreReferences?.hasMore} )}
open={canOpenAssignReferencesAttributeDialog} errors={channelErrors}
onFetch={handlers.fetchReferences} loading={loading}
onFetchMore={handlers.fetchMoreReferences?.onFetchMore} onChange={handlers.changeChannels}
loading={handlers.fetchMoreReferences?.loading} />
onClose={onCloseDialog} <CardSpacer />
onSubmit={attributeValues => <ProductVariantCheckoutSettings
handleAssignReferenceAttribute( data={data}
attributeValues, disabled={loading}
data, errors={errors}
handlers onChange={change}
) />
} <CardSpacer />
<ProductShipping
data={data}
disabled={loading}
errors={errors}
weightUnit={variant?.weight?.unit || defaultWeightUnit}
onChange={change}
/>
<CardSpacer />
<ProductStocks
productVariantChannelListings={data.channelListings.map(
channel => ({
...channel.data,
...channel.value
})
)}
onVariantChannelListingChange={handlers.changeChannels}
data={data}
disabled={loading}
hasVariants={true}
errors={errors}
formErrors={formErrors}
stocks={data.stocks}
warehouses={warehouses}
onChange={handlers.changeStock}
onFormDataChange={change}
onChangePreorderEndDate={handlers.changePreorderEndDate}
onEndPreorderTrigger={
!!variant?.preorder
? () => setIsEndPreorderModalOpened(true)
: null
}
onWarehouseStockAdd={handlers.addStock}
onWarehouseStockDelete={handlers.deleteStock}
onWarehouseConfigure={onWarehouseConfigure}
/>
<CardSpacer />
<Metadata data={data} onChange={handlers.changeMetadata} />
</div>
</Grid>
<Savebar
disabled={loading || formDisabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}
/> />
)} {canOpenAssignReferencesAttributeDialog && (
</> <AssignAttributeValueDialog
)} attributeValues={getAttributeValuesFromReferences(
assignReferencesAttributeId,
data.attributes,
referencePages,
referenceProducts
)}
hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences}
onFetchMore={handlers.fetchMoreReferences?.onFetchMore}
loading={handlers.fetchMoreReferences?.loading}
onClose={onCloseDialog}
onSubmit={attributeValues =>
handleAssignReferenceAttribute(
attributeValues,
data,
handlers
)
}
/>
)}
</>
);
}}
</ProductVariantUpdateForm> </ProductVariantUpdateForm>
</Container> </Container>
{variant && ( {variant && (
@ -400,7 +413,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onMediaSelect={onMediaSelect} onMediaSelect={onMediaSelect}
open={isModalOpened} open={isModalOpened}
media={productMedia} media={productMedia}
selectedMedia={maybe(() => variant.media.map(image => image.id))} selectedMedia={variant?.media.map(image => image.id)}
/> />
)} )}
{!!variant?.preorder && ( {!!variant?.preorder && (

View file

@ -225170,114 +225170,6 @@ exports[`Storyshots Views / Products / Product variant details when loading data
<div <div
class="CardSpacer-spacer-id" class="CardSpacer-spacer-id"
/> />
<div
class="MuiPaper-root-id MuiCard-root-id Attributes-card-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Variant Attributes
</span>
</div>
</div>
<div
class="MuiCardContent-root-id Attributes-cardContent-id"
>
<div
class="Attributes-expansionBar-id"
>
<div
class="Attributes-expansionBarLabelContainer-id"
>
<div
class="MuiTypography-root-id Attributes-expansionBarLabel-id MuiTypography-caption-id"
>
0 Attributes
</div>
</div>
<button
class="MuiButtonBase-root-id IconButton-secondary-id Attributes-expansionBarButton-id IconButton-hoverOutline-id"
data-test-id="attributes-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id Attributes-expansionBarButtonIcon-id Attributes-rotate-id"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</button>
</div>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiCard-root-id Attributes-card-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Variant Selection Attributes
</span>
</div>
</div>
<div
class="MuiCardContent-root-id Attributes-cardContent-id"
>
<div
class="Attributes-expansionBar-id"
>
<div
class="Attributes-expansionBarLabelContainer-id"
>
<div
class="MuiTypography-root-id Attributes-expansionBarLabel-id MuiTypography-caption-id"
>
0 Attributes
</div>
</div>
<button
class="MuiButtonBase-root-id IconButton-secondary-id Attributes-expansionBarButton-id IconButton-hoverOutline-id"
data-test-id="attributes-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id Attributes-expansionBarButtonIcon-id Attributes-rotate-id"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</button>
</div>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
<div <div
class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id" class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id"
> >