Skip to content

Commit

Permalink
feat(atomic): allow user to customize grid card click behavior/links (#…
Browse files Browse the repository at this point in the history
…4287)

Fixes #4267 
https://coveord.atlassian.net/browse/KIT-3470

---------

Co-authored-by: GitHub Actions Bot <>
  • Loading branch information
louis-bompart authored Aug 27, 2024
1 parent d6570ac commit e5961ca
Show file tree
Hide file tree
Showing 38 changed files with 678 additions and 319 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ packages/atomic/src/external-builds/**/*
packages/atomic/src/generated/**
packages/quantic/docs/out/quantic-docs.json
packages/samples/headless-react/build/**/*
packages/samples/angular/src/lang/*.json
packages/samples/angular/src/lang/*.json
packages/samples/vuejs/public/lang/*.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ AtomicCommerceFacets,
AtomicCommerceInterface,
AtomicCommerceLoadMoreProducts,
AtomicCommercePager,
AtomicCommerceProductList,
AtomicCommerceProductsPerPage,
AtomicCommerceQuerySummary,
AtomicCommerceRefineModal,
Expand Down Expand Up @@ -128,6 +129,7 @@ AtomicCommerceFacets,
AtomicCommerceInterface,
AtomicCommerceLoadMoreProducts,
AtomicCommercePager,
AtomicCommerceProductList,
AtomicCommerceProductsPerPage,
AtomicCommerceQuerySummary,
AtomicCommerceRefineModal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,29 @@ export declare interface AtomicCommercePager extends Components.AtomicCommercePa
}


@ProxyCmp({
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize', 'numberOfPlaceholders'],
methods: ['setRenderFunction']
})
@Component({
selector: 'atomic-commerce-product-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize', 'numberOfPlaceholders'],
})
export class AtomicCommerceProductList {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}


export declare interface AtomicCommerceProductList extends Components.AtomicCommerceProductList {}


@ProxyCmp({
inputs: ['choicesDisplayed', 'initialChoice']
})
Expand Down Expand Up @@ -1279,14 +1302,14 @@ export declare interface AtomicRelevanceInspector extends Components.AtomicRelev


@ProxyCmp({
inputs: ['classes', 'content', 'density', 'display', 'imageSize', 'result', 'stopPropagation']
inputs: ['classes', 'content', 'density', 'display', 'imageSize', 'linkContent', 'result', 'stopPropagation']
})
@Component({
selector: 'atomic-result',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['classes', 'content', 'density', 'display', 'imageSize', 'result', 'stopPropagation'],
inputs: ['classes', 'content', 'density', 'display', 'imageSize', 'linkContent', 'result', 'stopPropagation'],
})
export class AtomicResult {
protected el: HTMLElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import type {Product} from '@coveo/headless/commerce';
import React, {useEffect, useRef} from 'react';
import {createRoot} from 'react-dom/client';
import {renderToString} from 'react-dom/server';
import {AtomicCommerceProductList} from '../stencil-generated/commerce';
import {
AtomicCommerceProductList,
AtomicProductLink,
} from '../stencil-generated/commerce';

interface Template {
contentTemplate: JSX.Element;
linkTemplate: JSX.Element;
}

/**
* The properties of the AtomicCommerceProductList component
Expand All @@ -13,7 +21,7 @@ interface WrapperProps extends AtomicJSX.AtomicCommerceProductList {
* A template function that takes a result item and outputs its target rendering as a JSX element.
* It can be used to conditionally render different type of result templates based on the properties of each result.
*/
template: (result: Product) => JSX.Element;
template: (result: Product) => JSX.Element | Template;
}

/**
Expand All @@ -27,12 +35,30 @@ export const ListWrapper: React.FC<WrapperProps> = (props) => {
const commerceProductListRef =
useRef<HTMLAtomicCommerceProductListElement>(null);
useEffect(() => {
commerceProductListRef.current?.setRenderFunction((result, root) => {
createRoot(root).render(template(result as Product));
return renderToString(template(result as Product));
});
commerceProductListRef.current?.setRenderFunction(
(product, root, linkContainer) => {
const templateResult = template(product as Product);
if (hasLinkTemplate(templateResult)) {
createRoot(linkContainer!).render(templateResult.linkTemplate);
createRoot(root).render(templateResult.contentTemplate);
return renderToString(templateResult.contentTemplate);
} else {
createRoot(root).render(templateResult);
createRoot(linkContainer!).render(
<AtomicProductLink></AtomicProductLink>
);
return renderToString(templateResult);
}
}
);
}, [commerceProductListRef]);
return (
<AtomicCommerceProductList ref={commerceProductListRef} {...otherProps} />
);
};

const hasLinkTemplate = (
template: JSX.Element | Template
): template is Template => {
return (template as Template).linkTemplate !== undefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import type {Product} from '@coveo/headless/commerce';
import React, {useEffect, useRef} from 'react';
import {createRoot} from 'react-dom/client';
import {renderToString} from 'react-dom/server';
import {AtomicCommerceRecommendationList} from '../stencil-generated/commerce';
import {
AtomicCommerceRecommendationList,
AtomicProductLink,
} from '../stencil-generated/commerce';

interface Template {
contentTemplate: JSX.Element;
linkTemplate: JSX.Element;
}

/**
* The properties of the AtomicCommerceRecommendationList component
Expand All @@ -13,7 +21,7 @@ interface WrapperProps extends AtomicJSX.AtomicCommerceRecommendationList {
* A template function that takes a result item and outputs its target rendering as a JSX element.
* It can be used to conditionally render different type of result templates based on the properties of each result.
*/
template: (result: Product) => JSX.Element;
template: (result: Product) => JSX.Element | Template;
}

/**
Expand All @@ -27,10 +35,22 @@ export const ListWrapper: React.FC<WrapperProps> = (props) => {
const commerceRecsListRef =
useRef<HTMLAtomicCommerceRecommendationListElement>(null);
useEffect(() => {
commerceRecsListRef.current?.setRenderFunction((result, root) => {
createRoot(root).render(template(result as Product));
return renderToString(template(result as Product));
});
commerceRecsListRef.current?.setRenderFunction(
(product, root, linkContainer) => {
const templateResult = template(product as Product);
if (hasLinkTemplate(templateResult)) {
createRoot(linkContainer!).render(templateResult.linkTemplate);
createRoot(root).render(templateResult.contentTemplate);
return renderToString(templateResult.contentTemplate);
} else {
createRoot(root).render(templateResult);
createRoot(linkContainer!).render(
<AtomicProductLink></AtomicProductLink>
);
return renderToString(templateResult);
}
}
);
}, [commerceRecsListRef]);
return (
<AtomicCommerceRecommendationList
Expand All @@ -39,3 +59,9 @@ export const ListWrapper: React.FC<WrapperProps> = (props) => {
/>
);
};

const hasLinkTemplate = (
template: JSX.Element | Template
): template is Template => {
return (template as Template).linkTemplate !== undefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type {Result} from '@coveo/headless/recommendation';
import React, {useEffect, useRef} from 'react';
import {createRoot} from 'react-dom/client';
import {renderToString} from 'react-dom/server';
import {AtomicRecsList} from '../stencil-generated/search';
import {AtomicRecsList, AtomicResultLink} from '../stencil-generated/search';

interface Template {
contentTemplate: JSX.Element;
linkTemplate: JSX.Element;
}

/**
* The properties of the AtomicRecsList component
Expand All @@ -13,7 +18,7 @@ interface WrapperProps extends AtomicJSX.AtomicRecsList {
* A template function that takes a result item and outputs its target rendering as a JSX element.
* It can be used to conditionally render different type of result templates based on the properties of each result.
*/
template: (result: Result) => JSX.Element;
template: (result: Result) => JSX.Element | Template;
}

/**
Expand All @@ -26,10 +31,26 @@ export const RecsListWrapper: React.FC<WrapperProps> = (props) => {
const {template, ...otherProps} = props;
const recsListRef = useRef<HTMLAtomicRecsListElement>(null);
useEffect(() => {
recsListRef.current?.setRenderFunction((result, root) => {
createRoot(root).render(template(result as Result));
return renderToString(template(result as Result));
recsListRef.current?.setRenderFunction((result, root, linkContainer) => {
const templateResult = template(result as Result);
if (hasLinkTemplate(templateResult)) {
createRoot(linkContainer!).render(templateResult.linkTemplate);
createRoot(root).render(templateResult.contentTemplate);
return renderToString(templateResult.contentTemplate);
} else {
createRoot(root).render(templateResult);
createRoot(linkContainer!).render(
<AtomicResultLink></AtomicResultLink>
);
return renderToString(templateResult);
}
});
}, [recsListRef]);
return <AtomicRecsList ref={recsListRef} {...otherProps} />;
};

const hasLinkTemplate = (
template: JSX.Element | Template
): template is Template => {
return (template as Template).linkTemplate !== undefined;
};
31 changes: 26 additions & 5 deletions packages/atomic-react/src/components/search/ResultListWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type {Result} from '@coveo/headless';
import React, {useEffect, useRef} from 'react';
import {createRoot} from 'react-dom/client';
import {renderToString} from 'react-dom/server';
import {AtomicResultList} from '../stencil-generated/search';
import {AtomicResultLink, AtomicResultList} from '../stencil-generated/search';

interface Template {
contentTemplate: JSX.Element;
linkTemplate: JSX.Element;
}

/**
* The properties of the AtomicResultList component
Expand All @@ -13,7 +18,7 @@ interface WrapperProps extends AtomicJSX.AtomicResultList {
* A template function that takes a result item and outputs its target rendering as a JSX element.
* It can be used to conditionally render different type of result templates based on the properties of each result.
*/
template: (result: Result) => JSX.Element;
template: (result: Result) => JSX.Element | Template;
}

/**
Expand All @@ -26,10 +31,26 @@ export const ResultListWrapper: React.FC<WrapperProps> = (props) => {
const {template, ...otherProps} = props;
const resultListRef = useRef<HTMLAtomicResultListElement>(null);
useEffect(() => {
resultListRef.current?.setRenderFunction((result, root) => {
createRoot(root).render(template(result as Result));
return renderToString(template(result as Result));
resultListRef.current?.setRenderFunction((result, root, linkContainer) => {
const templateResult = template(result as Result);
if (hasLinkTemplate(templateResult)) {
createRoot(linkContainer!).render(templateResult.linkTemplate);
createRoot(root).render(templateResult.contentTemplate);
return renderToString(templateResult.contentTemplate);
} else {
createRoot(root).render(templateResult);
createRoot(linkContainer!).render(
<AtomicResultLink></AtomicResultLink>
);
return renderToString(templateResult);
}
});
}, [resultListRef]);
return <AtomicResultList ref={resultListRef} {...otherProps} />;
};

const hasLinkTemplate = (
template: JSX.Element | Template
): template is Template => {
return (template as Template).linkTemplate !== undefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('Result Link Component', () => {
});

it('should render an "atomic-result-text" component containing the title', () => {
ResultLinkSelectors.firstInResult().should('have.text', title);
ResultLinkSelectors.firstInResult().first().should('have.text', title);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const resultTextComponent = 'atomic-result-text';
export const ResultTextSelectors = {
shadow: () => cy.get(resultTextComponent),
firstInResult: () =>
ResultListSelectors.firstResult().find(resultTextComponent),
ResultListSelectors.firstResult().find(resultTextComponent).first(),
highlight: () => ResultTextSelectors.firstInResult().find('b'),
};
Loading

0 comments on commit e5961ca

Please sign in to comment.