diff --git a/packages/instantsearch.js/src/connectors/hits/connectHits.ts b/packages/instantsearch.js/src/connectors/hits/connectHits.ts index a8c47bc0f4..8590a2538a 100644 --- a/packages/instantsearch.js/src/connectors/hits/connectHits.ts +++ b/packages/instantsearch.js/src/connectors/hits/connectHits.ts @@ -25,6 +25,12 @@ const withUsage = createDocumentationMessageGenerator({ connector: true, }); +type Banner = NonNullable< + NonNullable< + Required['renderingContent']> + >['widgets']['banners'] +>[number]; + export type HitsRenderState = { /** * The matched hits from Algolia API. @@ -36,6 +42,11 @@ export type HitsRenderState = { */ results?: SearchResults>; + /** + * The banner to display above the hits. + */ + banner?: Banner; + /** * Sends an event to the Insights middleware. */ diff --git a/packages/instantsearch.js/src/widgets/hits/__tests__/hits.test.tsx b/packages/instantsearch.js/src/widgets/hits/__tests__/hits.test.tsx index d3e58842c5..b057b12ad0 100644 --- a/packages/instantsearch.js/src/widgets/hits/__tests__/hits.test.tsx +++ b/packages/instantsearch.js/src/widgets/hits/__tests__/hits.test.tsx @@ -20,6 +20,21 @@ import hits from '../hits'; import type { SearchResponse } from '../../../../src/types'; +const bannerWidgetRenderingContent = { + widgets: { + banners: [ + { + image: { + urls: [{ url: 'https://via.placeholder.com/550x250' }], + }, + link: { + url: 'https://www.algolia.com', + }, + }, + ], + }, +}; + beforeEach(() => { document.body.innerHTML = ''; }); @@ -50,7 +65,11 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" test('adds custom CSS classes', async () => { const container = document.createElement('div'); - const searchClient = createMockedSearchClient(); + const searchClient = createMockedSearchClient({ + // @TODO: remove once algoliasearch js client has been updated + // @ts-expect-error + renderingContent: bannerWidgetRenderingContent, + }); const search = instantsearch({ indexName: 'indexName', @@ -73,6 +92,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" emptyRoot: 'EMPTY_ROOT', list: 'LIST', item: 'ITEM', + bannerRoot: 'BANNER_ROOT', + bannerImage: 'BANNER_IMAGE', + bannerLink: 'BANNER_LINK', }, }), ]); @@ -84,6 +106,15 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" expect(container.querySelector('.ais-Hits')).toHaveClass('ROOT'); expect(container.querySelector('.ais-Hits-list')).toHaveClass('LIST'); expect(container.querySelector('.ais-Hits-item')).toHaveClass('ITEM'); + expect(container.querySelector('.ais-Hits-banner')).toHaveClass( + 'BANNER_ROOT' + ); + expect(container.querySelector('.ais-Hits-banner-image')).toHaveClass( + 'BANNER_IMAGE' + ); + expect(container.querySelector('.ais-Hits-banner-link')).toHaveClass( + 'BANNER_LINK' + ); }); type CustomHit = { name: string; description: string }; @@ -276,7 +307,11 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" test('renders with templates using `html`', async () => { const container = document.createElement('div'); const searchBoxContainer = document.createElement('div'); - const searchClient = createMockedSearchClient(); + const searchClient = createMockedSearchClient({ + // @TODO: remove once algoliasearch js client has been updated + // @ts-expect-error + renderingContent: bannerWidgetRenderingContent, + }); const search = instantsearch({ indexName: 'indexName', searchClient }); @@ -303,6 +338,11 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" empty({ query }, { html }) { return html`

No results for ${query}

`; }, + banner({ banner }, { html }) { + // @TODO: remove once algoliasearch js client has been updated + // @ts-expect-error + return html``; + }, }, }), ]); @@ -316,6 +356,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/"
+
    @@ -491,6 +534,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/"
    +

    No results for @@ -505,7 +551,11 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/" test('renders with templates using JSX', async () => { const container = document.createElement('div'); const searchBoxContainer = document.createElement('div'); - const searchClient = createMockedSearchClient(); + const searchClient = createMockedSearchClient({ + // @TODO: remove once algoliasearch js client has been updated + // @ts-expect-error + renderingContent: bannerWidgetRenderingContent, + }); const search = instantsearch({ indexName: 'indexName', searchClient }); @@ -542,6 +592,11 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/"

    ); }, + banner({ banner }) { + // @TODO: remove once algoliasearch js client has been updated + // @ts-expect-error + return ; + }, }, }), ]); @@ -555,6 +610,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/"
    +
      @@ -730,6 +788,9 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/"
      +

      No results for diff --git a/packages/instantsearch.js/src/widgets/hits/defaultTemplates.ts b/packages/instantsearch.js/src/widgets/hits/defaultTemplates.ts index 2b40f6e39c..4ac431297d 100644 --- a/packages/instantsearch.js/src/widgets/hits/defaultTemplates.ts +++ b/packages/instantsearch.js/src/widgets/hits/defaultTemplates.ts @@ -2,7 +2,7 @@ import { omit } from '../../lib/utils'; import type { HitsTemplates } from './hits'; -const defaultTemplates: Required = { +const defaultTemplates: HitsTemplates = { empty() { return 'No results'; }, diff --git a/packages/instantsearch.js/src/widgets/hits/hits.tsx b/packages/instantsearch.js/src/widgets/hits/hits.tsx index d74bde9815..642892434e 100644 --- a/packages/instantsearch.js/src/widgets/hits/hits.tsx +++ b/packages/instantsearch.js/src/widgets/hits/hits.tsx @@ -49,7 +49,7 @@ const renderer = containerNode: HTMLElement; cssClasses: HitsCSSClasses; renderState: { - templateProps?: PreparedTemplateProps>; + templateProps?: PreparedTemplateProps; }; templates: HitsTemplates; }): Renderer> => @@ -61,6 +61,7 @@ const renderer = insights, bindEvent, sendEvent, + banner, }, isFirstRendering ) => { @@ -127,6 +128,17 @@ const renderer = /> ); + const bannerComponent: HitsUiComponentProps['bannerComponent'] = ( + props + ) => ( + + ); + render( , containerNode ); @@ -160,6 +174,14 @@ export type HitsTemplates = Partial<{ __hitIndex: number; } >; + + /** + * Template to use for the banner. + */ + banner: Template<{ + banner: Required; + className: string; + }>; }>; export type HitsWidgetParams = {