From b55881a15a06bed4dbd9bf37c2f1f9584e77042a Mon Sep 17 00:00:00 2001 From: Raed Date: Mon, 10 Feb 2025 08:15:54 +0100 Subject: [PATCH 1/4] feat(recommend): add Trending-Facets model --- examples/js/getting-started/src/app.js | 13 +- examples/react/getting-started/src/App.tsx | 20 +- .../src/components/Carousel.tsx | 55 ++-- .../src/components/TrendingFacets.tsx | 167 +++++++++++ .../src/components/index.ts | 1 + .../instantsearch.js/src/connectors/index.ts | 2 + .../trending-facets/connectTrendingFacets.ts | 175 +++++++++++ .../src/connectors/trending-facets/types.ts | 6 + .../instantsearch.js/src/widgets/index.ts | 1 + .../trending-facets/trending-facets.tsx | 273 ++++++++++++++++++ .../src/connectors/useTrendingFacets.ts | 26 ++ .../react-instantsearch-core/src/index.ts | 1 + .../src/widgets/TrendingFacets.tsx | 92 ++++++ .../react-instantsearch/src/widgets/index.ts | 1 + 14 files changed, 790 insertions(+), 43 deletions(-) create mode 100644 packages/instantsearch-ui-components/src/components/TrendingFacets.tsx create mode 100644 packages/instantsearch.js/src/connectors/trending-facets/connectTrendingFacets.ts create mode 100644 packages/instantsearch.js/src/connectors/trending-facets/types.ts create mode 100644 packages/instantsearch.js/src/widgets/trending-facets/trending-facets.tsx create mode 100644 packages/react-instantsearch-core/src/connectors/useTrendingFacets.ts create mode 100644 packages/react-instantsearch/src/widgets/TrendingFacets.tsx diff --git a/examples/js/getting-started/src/app.js b/examples/js/getting-started/src/app.js index 88bfc54a49..1aff8d1171 100644 --- a/examples/js/getting-started/src/app.js +++ b/examples/js/getting-started/src/app.js @@ -8,7 +8,7 @@ import { panel, refinementList, searchBox, - trendingItems, + trendingFacets, } from 'instantsearch.js/es/widgets'; import 'instantsearch.css/themes/satellite.css'; @@ -56,19 +56,14 @@ search.addWidgets([ pagination({ container: '#pagination', }), - trendingItems({ + trendingFacets({ container: '#trending', limit: 6, + facetName: 'brand', templates: { item: (item, { html }) => html`
- +

${item.facetName}: ${item.facetValue}

`, layout: carousel(), diff --git a/examples/react/getting-started/src/App.tsx b/examples/react/getting-started/src/App.tsx index 857391877e..870229ae6f 100644 --- a/examples/react/getting-started/src/App.tsx +++ b/examples/react/getting-started/src/App.tsx @@ -9,7 +9,7 @@ import { Pagination, RefinementList, SearchBox, - TrendingItems, + TrendingFacets, Carousel, } from 'react-instantsearch'; @@ -61,8 +61,8 @@ export function App() {
- @@ -96,17 +96,3 @@ function HitComponent({ hit }: { hit: HitType }) { ); } - -function ItemComponent({ item }: { item: Hit }) { - return ( -
- -
- ); -} diff --git a/packages/instantsearch-ui-components/src/components/Carousel.tsx b/packages/instantsearch-ui-components/src/components/Carousel.tsx index 7916447a37..46d77752b7 100644 --- a/packages/instantsearch-ui-components/src/components/Carousel.tsx +++ b/packages/instantsearch-ui-components/src/components/Carousel.tsx @@ -2,6 +2,7 @@ import { cx } from '../lib'; import { createDefaultItemComponent } from './recommend-shared'; +import { isTrendingFacetHit } from './TrendingFacets'; import type { ComponentProps, @@ -11,6 +12,7 @@ import type { Renderer, SendEventForHits, } from '../types'; +import type { TrendingFacetHit } from './TrendingFacets'; export type CarouselProps< TObject, @@ -20,7 +22,7 @@ export type CarouselProps< nextButtonRef: MutableRef; previousButtonRef: MutableRef; carouselIdRef: MutableRef; - items: Array>; + items: Array | TrendingFacetHit>; itemComponent?: ( props: RecommendItemComponentProps> & TComponentProps @@ -232,22 +234,41 @@ export function createCarouselComponent({ createElement, Fragment }: Renderer) { } }} > - {items.map((item, index) => ( -
  • { - sendEvent('click:internal', item, 'Item Clicked'); - }} - onAuxClick={() => { - sendEvent('click:internal', item, 'Item Clicked'); - }} - > - -
  • - ))} + {items.map((item, index) => + isTrendingFacetHit(item) ? ( +
  • + + } + sendEvent={sendEvent} + /> +
  • + ) : ( +
  • { + sendEvent('click:internal', item, 'Item Clicked'); + }} + onAuxClick={() => { + sendEvent('click:internal', item, 'Item Clicked'); + }} + > + +
  • + ) + )}
    diff --git a/packages/instantsearch-ui-components/src/components/Carousel.tsx b/packages/instantsearch-ui-components/src/components/Carousel.tsx index 58f2d5b906..2ced71318a 100644 --- a/packages/instantsearch-ui-components/src/components/Carousel.tsx +++ b/packages/instantsearch-ui-components/src/components/Carousel.tsx @@ -12,8 +12,8 @@ import type { RecordWithObjectID, Renderer, SendEventForHits, + TrendingFacetHit, } from '../types'; -import type { TrendingFacetHit } from 'algoliasearch'; export type CarouselProps< TObject, diff --git a/packages/instantsearch-ui-components/src/components/TrendingFacets.tsx b/packages/instantsearch-ui-components/src/components/TrendingFacets.tsx index b075aeda6b..f97d63077d 100644 --- a/packages/instantsearch-ui-components/src/components/TrendingFacets.tsx +++ b/packages/instantsearch-ui-components/src/components/TrendingFacets.tsx @@ -5,7 +5,6 @@ import { cx } from '../lib'; import { createDefaultEmptyComponent, createDefaultHeaderComponent, - createDefaultItemComponent, } from './recommend-shared'; import type { @@ -18,8 +17,8 @@ import type { RecordWithObjectID, Renderer, SendEventForHits, + TrendingFacetHit, } from '../types'; -import type { TrendingFacetHit } from 'algoliasearch'; type TrendingFacetLayoutProps> = { classNames: TClassNames; @@ -33,7 +32,7 @@ type TrendingFacetLayoutProps> = { export type TrendingFacetsComponentProps< TComponentProps extends Record = Record > = { - itemComponent?: ( + itemComponent: ( props: RecommendItemComponentProps & TComponentProps ) => JSX.Element; items: TrendingFacetHit[]; @@ -69,10 +68,7 @@ export function createTrendingFacetsComponent({ createElement, Fragment, }), - itemComponent: ItemComponent = createDefaultItemComponent({ - createElement, - Fragment, - }), + itemComponent: ItemComponent, layout: Layout = createListComponent({ createElement, Fragment }), items, status, diff --git a/packages/instantsearch-ui-components/src/types/Recommend.ts b/packages/instantsearch-ui-components/src/types/Recommend.ts index be43ddf7fb..0c39f36f43 100644 --- a/packages/instantsearch-ui-components/src/types/Recommend.ts +++ b/packages/instantsearch-ui-components/src/types/Recommend.ts @@ -100,3 +100,21 @@ export type RecommendItemComponentProps = { // @TODO: use instantsearch status instead export type RecommendStatus = 'idle' | 'loading' | 'stalled' | 'error'; + +// for convenience, redefined here from instantsearch +export type TrendingFacetHit = { + /** + * Recommendation score. + */ + _score: number; + + /** + * Facet attribute. To be used in combination with `facetValue`. If specified, only recommendations matching the facet filter will be returned. + */ + facetName: string; + + /** + * Facet value. To be used in combination with `facetName`. If specified, only recommendations matching the facet filter will be returned. + */ + facetValue: string; +}; diff --git a/packages/instantsearch.js/src/widgets/trending-facets/trending-facets.tsx b/packages/instantsearch.js/src/widgets/trending-facets/trending-facets.tsx index 3f6e8cd3ba..7cf8c708ec 100644 --- a/packages/instantsearch.js/src/widgets/trending-facets/trending-facets.tsx +++ b/packages/instantsearch.js/src/widgets/trending-facets/trending-facets.tsx @@ -99,7 +99,7 @@ function createRenderer({ /> ); } - : undefined; + : () =>
    ; const emptyComponent = ( templates.empty @@ -180,11 +180,6 @@ export type TrendingFacetsTemplates = Partial<{ > & { cssClasses: RecommendClassNames } >; - /** - * Template to use for each result. This template will receive an object containing a single record. - */ - item: TemplateWithBindEvent; - /** * Template to use to wrap all items. */ @@ -201,7 +196,9 @@ export type TrendingFacetsTemplates = Partial<{ cssClasses: Pick; } >; -}>; +}> & { + item: TemplateWithBindEvent; +}; type TrendingFacetsWidgetParams = { /** @@ -212,7 +209,7 @@ type TrendingFacetsWidgetParams = { /** * Templates to customize the widget. */ - templates?: TrendingFacetsTemplates; + templates: TrendingFacetsTemplates; /** * CSS classes to add to the widget elements. @@ -238,7 +235,7 @@ export default (function trendingFacets( threshold, escapeHTML, transformItems, - templates = {}, + templates, cssClasses = {}, } = widgetParams || {}; diff --git a/packages/react-instantsearch/src/widgets/TrendingFacets.tsx b/packages/react-instantsearch/src/widgets/TrendingFacets.tsx index 35afb5e8ae..573260af0f 100644 --- a/packages/react-instantsearch/src/widgets/TrendingFacets.tsx +++ b/packages/react-instantsearch/src/widgets/TrendingFacets.tsx @@ -1,5 +1,5 @@ import { createTrendingFacetsComponent } from 'instantsearch-ui-components'; -import React, { createElement, Fragment, useMemo } from 'react'; +import React, { createElement, Fragment } from 'react'; import { useInstantSearch, useTrendingFacets } from 'react-instantsearch-core'; import type { @@ -24,7 +24,7 @@ export type TrendingFacetsProps = Omit< keyof UiProps > & UseTrendingFacetsProps & { - itemComponent?: TrendingFacetsUiComponentProps['itemComponent']; + itemComponent: TrendingFacetsUiComponentProps['itemComponent']; headerComponent?: TrendingFacetsUiComponentProps['headerComponent']; emptyComponent?: TrendingFacetsUiComponentProps['emptyComponent']; layoutComponent?: TrendingFacetsUiComponentProps['layout']; @@ -70,17 +70,9 @@ export function TrendingFacets({ }) : undefined; - const _itemComponent: typeof itemComponent = useMemo( - () => - itemComponent - ? (itemProps) => itemComponent({ ...itemProps }) - : undefined, - [itemComponent] - ); - const uiProps: UiProps = { items, - itemComponent: _itemComponent, + itemComponent, headerComponent, emptyComponent, layout,