Skip to content

Commit

Permalink
fix(atomic): fix facet and product list loading state (#4080)
Browse files Browse the repository at this point in the history
* Set the `loadingFlag` in `atomic-commerce-interface`: We need this to
be set, otherwise components wrongly assume that "everything is loaded
and ready to go". For end user, this fixes an issue with
`atomic-product-list` removing placeholders too quickly without anything
to render except a big blank empty state.

* Use a property `numberOfPlaceholders` to decide the number of
placeholder to display for the product list: using the number of
products in headless state means its always 0, because the products are
still being loaded/not yet returned from the backend yet. Making the
placeholder useless otherwise.

* Add a `<FacetPlaceholder />` display in `atomic-commerce-facets` while
the app is being loaded (ie: while the product list is still rendering a
placeholder). This reduces the (annoying) layout shifts.

* Remove `random()` usage from `<FacetPlaceholder />`: This causes weird
flicker issue with the placeholder `width` changing on every render
loop.

https://coveord.atlassian.net/browse/KIT-3311
  • Loading branch information
olamothe authored Jun 13, 2024
1 parent 6ea3055 commit 6500ea1
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 14 deletions.
8 changes: 8 additions & 0 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,10 @@ export namespace Components {
* The expected size of the image displayed for products.
*/
"imageSize": ItemDisplayImageSize;
/**
* The desired number of placeholders to display while the product list is loading.
*/
"numberOfPlaceholders": number;
/**
* Sets a rendering function to bypass the standard HTML template mechanism for rendering products. You can use this function while working with web frameworks that don't use plain HTML syntax, e.g., React, Angular or Vue. Do not use this method if you integrate Atomic in a plain HTML deployment.
* @param productRenderingFunction
Expand Down Expand Up @@ -5845,6 +5849,10 @@ declare namespace LocalJSX {
* The expected size of the image displayed for products.
*/
"imageSize"?: ItemDisplayImageSize;
/**
* The desired number of placeholders to display while the product list is loading.
*/
"numberOfPlaceholders"?: number;
}
/**
* The `atomic-commerce-query-error` component handles fatal errors when performing a query on the Commerce API. When the error is known, it displays a link to relevant documentation for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export class AtomicCommerceInterface
}

public connectedCallback() {
this.store.setLoadingFlag(FirstSearchExecutedFlag);
this.i18nClone = this.i18n.cloneInstance();
this.i18n.addResourceBundle = (
lng: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export class AtomicCommerceProductList
@State() public error!: Error;
@State() private templateHasError = false;

/**
* The desired number of placeholders to display while the product list is loading.
*/
@Prop({reflect: true}) numberOfPlaceholders = 24;

/**
* The desired layout to use when displaying products. Layouts affect how many products to display per row and how visually distinct they are from each other.
*/
Expand Down Expand Up @@ -188,7 +193,7 @@ export class AtomicCommerceProductList
display={this.display}
imageSize={this.imageSize}
displayPlaceholders={!this.bindings.store.isAppLoaded()}
numberOfPlaceholders={this.productState.products.length}
numberOfPlaceholders={this.numberOfPlaceholders}
></ResultsPlaceholdersGuard>
<ItemDisplayGuard
firstRequestExecuted={!!this.productState.responseId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
ListingSummary,
SearchSummary,
} from '@coveo/headless/commerce';
import {Component, h, Element, Host, State, Prop} from '@stencil/core';
import {Component, h, Element, State, Prop, Fragment} from '@stencil/core';
import {
BindStateToController,
InitializableComponent,
InitializeBindings,
} from '../../../../utils/initialization-utils';
import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder';
import {CommerceBindings as Bindings} from '../../atomic-commerce-interface/atomic-commerce-interface';

/**
Expand All @@ -35,6 +36,7 @@ import {CommerceBindings as Bindings} from '../../atomic-commerce-interface/atom
export class AtomicCommerceFacets implements InitializableComponent<Bindings> {
@InitializeBindings() public bindings!: Bindings;
public facetGenerator!: FacetGenerator;
public summary!: ListingSummary | SearchSummary;
@Element() host!: HTMLElement;

/**
Expand All @@ -48,8 +50,7 @@ export class AtomicCommerceFacets implements InitializableComponent<Bindings> {

@BindStateToController('facetGenerator')
@State()
public facetGeneratorState!: FacetGeneratorState[];
public summary!: ListingSummary | SearchSummary;
public facetGeneratorState!: FacetGeneratorState;

@State() public error!: Error;

Expand Down Expand Up @@ -90,8 +91,13 @@ export class AtomicCommerceFacets implements InitializableComponent<Bindings> {
}

public render() {
if (!this.bindings.store.isAppLoaded()) {
return [...Array.from({length: this.collapseFacetsAfter})].map(() => (
<FacetPlaceholder isCollapsed={false} numberOfValues={8} />
));
}
return (
<Host>
<Fragment>
{this.facetGenerator.facets.map((facet, index) => {
if (facet.state.values.length === 0) {
return;
Expand Down Expand Up @@ -136,7 +142,7 @@ export class AtomicCommerceFacets implements InitializableComponent<Bindings> {
}
}
})}
</Host>
</Fragment>
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {FunctionalComponent, h} from '@stencil/core';
import {getRandomArbitrary} from '../../../../utils/utils';

export interface FacetPlaceholderProps {
numberOfValues: number;
Expand All @@ -12,10 +11,11 @@ export const FacetPlaceholder: FunctionalComponent<FacetPlaceholderProps> = ({
}) => {
const facetValues = [];
for (let i = 0; i < numberOfValues; i++) {
const width = `${getRandomArbitrary(60, 100)}%`;
const opacity = `${getRandomArbitrary(0.3, 1)}`;
facetValues.push(
<div class="flex bg-neutral h-5 mt-4" style={{width, opacity}}></div>
<div
class="flex bg-neutral h-5 mt-4"
style={{width: '100%', opacity: '0.5'}}
></div>
);
}

Expand All @@ -25,10 +25,7 @@ export const FacetPlaceholder: FunctionalComponent<FacetPlaceholderProps> = ({
class="bg-background animate-pulse border border-neutral rounded-lg mb-4 p-7"
aria-hidden="true"
>
<div
class="bg-neutral rounded h-8"
style={{width: `${getRandomArbitrary(25, 75)}%`}}
></div>
<div class="bg-neutral rounded h-8" style={{width: '75%'}}></div>
{!isCollapsed && <div class="mt-7">{facetValues}</div>}
</div>
);
Expand Down

0 comments on commit 6500ea1

Please sign in to comment.