Skip to content

Commit

Permalink
feat(commerce): atomic-product-field-condition (#3959)
Browse files Browse the repository at this point in the history
Related to #3956

Adds the `atomic-product-field-condition` component to enable
conditional rendering based on commerce-specific fields in Coveo's UI
Kit.
- Implements the component with properties `ifDefined` and
`ifNotDefined` to check for the presence or absence of specified fields.
- Introduces `mustMatch` and `mustNotMatch` properties for conditional
rendering based on matching field values, utilizing
`ProductTemplatesHelpers`.
- Ensures the component evaluates conditions on component load and
removes itself from the DOM if conditions are not met, maintaining clean
template structure.
- Utilizes `@ProductContext` to access the current product's properties
for condition evaluation.


---
KIT-3058

For more details, open the [Copilot Workspace
session](https://copilot-workspace.githubnext.com/coveo/ui-kit/issues/3956?shareId=c1d4fa4c-3d58-4aab-a30d-db1259b300e5).

---------

Co-authored-by: GitHub Actions Bot <>
Co-authored-by: ylakhdar <[email protected]>
  • Loading branch information
louis-bompart and y-lakhdar authored Jun 1, 2024
1 parent a32fb64 commit 5e14eec
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const AtomicNumericRange = /*@__PURE__*/createReactComponent<JSX.AtomicNu
export const AtomicProduct = /*@__PURE__*/createReactComponent<JSX.AtomicProduct, HTMLAtomicProductElement>('atomic-product');
export const AtomicProductChildren = /*@__PURE__*/createReactComponent<JSX.AtomicProductChildren, HTMLAtomicProductChildrenElement>('atomic-product-children');
export const AtomicProductDescription = /*@__PURE__*/createReactComponent<JSX.AtomicProductDescription, HTMLAtomicProductDescriptionElement>('atomic-product-description');
export const AtomicProductFieldCondition = /*@__PURE__*/createReactComponent<JSX.AtomicProductFieldCondition, HTMLAtomicProductFieldConditionElement>('atomic-product-field-condition');
export const AtomicProductImage = /*@__PURE__*/createReactComponent<JSX.AtomicProductImage, HTMLAtomicProductImageElement>('atomic-product-image');
export const AtomicProductLink = /*@__PURE__*/createReactComponent<JSX.AtomicProductLink, HTMLAtomicProductLinkElement>('atomic-product-link');
export const AtomicProductNumericFieldValue = /*@__PURE__*/createReactComponent<JSX.AtomicProductNumericFieldValue, HTMLAtomicProductNumericFieldValueElement>('atomic-product-numeric-field-value');
Expand Down
45 changes: 45 additions & 0 deletions packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,20 @@ export namespace Components {
*/
"truncateAfter": 'none' | '1' | '2' | '3' | '4';
}
/**
* The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined.
* The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`).
*/
interface AtomicProductFieldCondition {
/**
* Verifies whether the specified fields are defined.
*/
"ifDefined"?: string;
/**
* Verifies whether the specified fields are not defined.
*/
"ifNotDefined"?: string;
}
/**
* The `atomic-product-image` component renders an image from a product field.
*/
Expand Down Expand Up @@ -4285,6 +4299,16 @@ declare global {
prototype: HTMLAtomicProductDescriptionElement;
new (): HTMLAtomicProductDescriptionElement;
};
/**
* The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined.
* The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`).
*/
interface HTMLAtomicProductFieldConditionElement extends Components.AtomicProductFieldCondition, HTMLStencilElement {
}
var HTMLAtomicProductFieldConditionElement: {
prototype: HTMLAtomicProductFieldConditionElement;
new (): HTMLAtomicProductFieldConditionElement;
};
/**
* The `atomic-product-image` component renders an image from a product field.
*/
Expand Down Expand Up @@ -5315,6 +5339,7 @@ declare global {
"atomic-product": HTMLAtomicProductElement;
"atomic-product-children": HTMLAtomicProductChildrenElement;
"atomic-product-description": HTMLAtomicProductDescriptionElement;
"atomic-product-field-condition": HTMLAtomicProductFieldConditionElement;
"atomic-product-image": HTMLAtomicProductImageElement;
"atomic-product-link": HTMLAtomicProductLinkElement;
"atomic-product-numeric-field-value": HTMLAtomicProductNumericFieldValueElement;
Expand Down Expand Up @@ -7053,6 +7078,20 @@ declare namespace LocalJSX {
*/
"truncateAfter"?: 'none' | '1' | '2' | '3' | '4';
}
/**
* The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined.
* The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`).
*/
interface AtomicProductFieldCondition {
/**
* Verifies whether the specified fields are defined.
*/
"ifDefined"?: string;
/**
* Verifies whether the specified fields are not defined.
*/
"ifNotDefined"?: string;
}
/**
* The `atomic-product-image` component renders an image from a product field.
*/
Expand Down Expand Up @@ -8515,6 +8554,7 @@ declare namespace LocalJSX {
"atomic-product": AtomicProduct;
"atomic-product-children": AtomicProductChildren;
"atomic-product-description": AtomicProductDescription;
"atomic-product-field-condition": AtomicProductFieldCondition;
"atomic-product-image": AtomicProductImage;
"atomic-product-link": AtomicProductLink;
"atomic-product-numeric-field-value": AtomicProductNumericFieldValue;
Expand Down Expand Up @@ -8882,6 +8922,11 @@ declare module "@stencil/core" {
"atomic-product": LocalJSX.AtomicProduct & JSXBase.HTMLAttributes<HTMLAtomicProductElement>;
"atomic-product-children": LocalJSX.AtomicProductChildren & JSXBase.HTMLAttributes<HTMLAtomicProductChildrenElement>;
"atomic-product-description": LocalJSX.AtomicProductDescription & JSXBase.HTMLAttributes<HTMLAtomicProductDescriptionElement>;
/**
* The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined.
* The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`).
*/
"atomic-product-field-condition": LocalJSX.AtomicProductFieldCondition & JSXBase.HTMLAttributes<HTMLAtomicProductFieldConditionElement>;
/**
* The `atomic-product-image` component renders an image from a product field.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Product, ProductTemplateCondition} from '@coveo/headless/commerce';
import {Component, Prop, h, Element} from '@stencil/core';
import {MapProp} from '../../../utils/props-utils';
import {ProductContext} from '../product-template-components/product-template-decorators';
import {
makeDefinedConditions,
makeMatchConditions,
} from '../product-template/product-template-common';

/**
* The `atomic-product-field-condition` component takes a list of conditions that, if fulfilled, apply the template in which it's defined.
* The condition properties can be based on any top-level product property of the `product` object, not restricted to fields (e.g., `ec_name`).
* @internal
*/
@Component({
tag: 'atomic-product-field-condition',
shadow: false,
})
export class AtomicProductFieldCondition {
@Element() host!: HTMLElement;

/**
* Verifies whether the specified fields are defined.
*/
@Prop({reflect: true}) ifDefined?: string;
/**
* Verifies whether the specified fields are not defined.
*/
@Prop({reflect: true}) ifNotDefined?: string;

@MapProp({splitValues: true}) mustMatch: Record<string, string[]> = {};

@MapProp({splitValues: true}) mustNotMatch: Record<string, string[]> = {};

private conditions: ProductTemplateCondition[] = [];
private shouldBeRemoved = false;

@ProductContext() private product!: Product;

public componentWillLoad() {
this.conditions = makeDefinedConditions(this.ifDefined, this.ifNotDefined);
this.conditions.push(
...makeMatchConditions(this.mustMatch, this.mustNotMatch)
);
}

public render() {
if (!this.conditions.every((condition) => condition(this.product))) {
this.shouldBeRemoved = true;
return '';
}

return <slot />;
}

public componentDidLoad() {
this.shouldBeRemoved && this.host.remove();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ <h1>HOMEPAGE</h1>
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
</atomic-product-section-visual>
<atomic-product-section-metadata>
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
<atomic-product-rating field="ec_rating"></atomic-product-rating>
<atomic-product-field-condition if-defined="ec_brand">
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
</atomic-product-field-condition>
<atomic-product-field-condition if-defined="ec_brand">
<atomic-product-rating field="ec_rating"></atomic-product-rating>
</atomic-product-field-condition>
</atomic-product-section-metadata>
<atomic-product-section-emphasized>
<atomic-product-price currency="USD"></atomic-product-price>
Expand Down
24 changes: 18 additions & 6 deletions packages/atomic/src/pages/examples/commerce-website/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ <h1>Search page</h1>
<atomic-product-link class="font-bold"></atomic-product-link>
</atomic-product-section-name>
<atomic-product-section-visual>
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
<atomic-product-field-condition if-defined="ec_thumbnails">
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
</atomic-product-field-condition>
</atomic-product-section-visual>
<atomic-product-section-metadata>
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
<atomic-product-rating field="ec_rating"></atomic-product-rating>
<atomic-product-field-condition if-defined="ec_brand">
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
</atomic-product-field-condition>
<atomic-product-field-condition if-defined="ec_rating">
<atomic-product-rating field="ec_rating"></atomic-product-rating>
</atomic-product-field-condition>
</atomic-product-section-metadata>
<atomic-product-section-emphasized>
<atomic-product-price currency="USD"></atomic-product-price>
Expand Down Expand Up @@ -72,11 +78,17 @@ <h1>Search page</h1>
<atomic-product-link class="font-bold"></atomic-product-link>
</atomic-product-section-name>
<atomic-product-section-visual>
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
<atomic-product-field-condition if-defined="ec_thumbnails">
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
</atomic-product-field-condition>
</atomic-product-section-visual>
<atomic-product-section-metadata>
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
<atomic-product-rating field="ec_rating"></atomic-product-rating>
<atomic-product-field-condition if-defined="ec_brand">
<atomic-product-text field="ec_brand" class="block text-neutral-dark"></atomic-product-text>
</atomic-product-field-condition>
<atomic-product-field-condition if-defined="ec_rating">
<atomic-product-rating field="ec_rating"></atomic-product-rating>
</atomic-product-field-condition>
</atomic-product-section-metadata>
<atomic-product-section-emphasized>
<atomic-product-price currency="USD"></atomic-product-price>
Expand Down

0 comments on commit 5e14eec

Please sign in to comment.