diff --git a/src/widgets/index.js b/src/widgets/index.js index 82fd7531b1..b04b03514f 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -40,3 +40,6 @@ export { default as panel } from './panel/panel'; export { default as queryRuleCustomData, } from './query-rule-custom-data/query-rule-custom-data'; +export { + default as queryRuleContext, +} from './query-rule-context/query-rule-context'; diff --git a/src/widgets/query-rule-context/__tests__/query-rule-context-test.ts b/src/widgets/query-rule-context/__tests__/query-rule-context-test.ts new file mode 100644 index 0000000000..485adcc1cb --- /dev/null +++ b/src/widgets/query-rule-context/__tests__/query-rule-context-test.ts @@ -0,0 +1,27 @@ +import queryRuleContext from '../query-rule-context'; + +describe('queryRuleContext', () => { + describe('Usage', () => { + test('throws trackedFilters error without options', () => { + expect(() => { + // @ts-ignore + queryRuleContext(); + }).toThrowErrorMatchingInlineSnapshot(` +"The \`trackedFilters\` option is required. + +See documentation: https://www.algolia.com/doc/api-reference/widgets/query-rule-context/js/" +`); + }); + + test('throws trackedFilters error with empty options', () => { + expect(() => { + // @ts-ignore + queryRuleContext({}); + }).toThrowErrorMatchingInlineSnapshot(` +"The \`trackedFilters\` option is required. + +See documentation: https://www.algolia.com/doc/api-reference/widgets/query-rule-context/js/" +`); + }); + }); +}); diff --git a/src/widgets/query-rule-context/query-rule-context.tsx b/src/widgets/query-rule-context/query-rule-context.tsx new file mode 100644 index 0000000000..cd7262e9c2 --- /dev/null +++ b/src/widgets/query-rule-context/query-rule-context.tsx @@ -0,0 +1,33 @@ +import noop from 'lodash/noop'; +import { WidgetFactory } from '../../types'; +import { createDocumentationMessageGenerator } from '../../lib/utils'; +import connectQueryRules, { + ParamTrackedFilters, + ParamTransformRuleContexts, +} from '../../connectors/query-rules/connectQueryRules'; + +type QueryRulesWidgetParams = { + trackedFilters: ParamTrackedFilters; + transformRuleContexts?: ParamTransformRuleContexts; +}; + +type QueryRuleContext = WidgetFactory; + +const withUsage = createDocumentationMessageGenerator({ + name: 'query-rule-context', +}); + +const queryRuleContext: QueryRuleContext = ( + { trackedFilters, transformRuleContexts } = {} as QueryRulesWidgetParams +) => { + if (!trackedFilters) { + throw new Error(withUsage('The `trackedFilters` option is required.')); + } + + return connectQueryRules(noop)({ + trackedFilters, + transformRuleContexts, + }); +}; + +export default queryRuleContext; diff --git a/stories/query-rule-context.stories.ts b/stories/query-rule-context.stories.ts new file mode 100644 index 0000000000..22b8a38acc --- /dev/null +++ b/stories/query-rule-context.stories.ts @@ -0,0 +1,116 @@ +import { storiesOf } from '@storybook/html'; +import { withHits } from '../.storybook/decorators'; +import moviesPlayground from '../.storybook/playgrounds/movies'; + +type CustomDataItem = { + title: string; + banner: string; + link: string; +}; + +const searchOptions = { + appId: 'latency', + apiKey: 'af044fb0788d6bb15f807e4420592bc5', + indexName: 'instant_search_movies', + playground: moviesPlayground, +}; + +storiesOf('QueryRuleContext', module) + .add( + 'default', + withHits(({ search, container, instantsearch }) => { + const widgetContainer = document.createElement('div'); + const description = document.createElement('ul'); + description.innerHTML = ` +
  • Select the "Drama" category and The Shawshank Redemption appears
  • +
  • Select the "Thriller" category and Pulp Fiction appears
  • +
  • Type music and a banner will appear.
  • + `; + + container.appendChild(description); + container.appendChild(widgetContainer); + + search.addWidget( + instantsearch.widgets.queryRuleContext({ + trackedFilters: { + genre: () => ['Thriller', 'Drama'], + }, + }) + ); + + search.addWidget( + instantsearch.widgets.queryRuleCustomData({ + container: widgetContainer, + transformItems: (items: CustomDataItem[]) => items[0], + templates: { + default({ title, banner, link }: CustomDataItem) { + if (!banner) { + return ''; + } + + return ` +

    ${title}

    + + + ${title} + + `; + }, + }, + }) + ); + }, searchOptions) + ) + .add( + 'with initial filter', + withHits(({ search, container, instantsearch }) => { + const widgetContainer = document.createElement('div'); + const description = document.createElement('ul'); + description.innerHTML = ` +
  • Select the "Drama" category and The Shawshank Redemption appears
  • +
  • Select the "Thriller" category and Pulp Fiction appears
  • +
  • Type music and a banner will appear.
  • + `; + + container.appendChild(description); + container.appendChild(widgetContainer); + + search.addWidget( + instantsearch.widgets.configure({ + disjunctiveFacetsRefinements: { + genre: ['Drama'], + }, + }) + ); + + search.addWidget( + instantsearch.widgets.queryRuleContext({ + trackedFilters: { + genre: () => ['Thriller', 'Drama'], + }, + }) + ); + + search.addWidget( + instantsearch.widgets.queryRuleCustomData({ + container: widgetContainer, + transformItems: (items: CustomDataItem[]) => items[0], + templates: { + default({ title, banner, link }: CustomDataItem) { + if (!banner) { + return ''; + } + + return ` +

    ${title}

    + + + ${title} + + `; + }, + }, + }) + ); + }, searchOptions) + );