From d3cd9c37ae4230227834ab4fabf7b00423b5cff2 Mon Sep 17 00:00:00 2001 From: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> Date: Fri, 10 Sep 2021 18:29:55 +0200 Subject: [PATCH] feat(plugins): introduce Tags plugin (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Chalifour --- .circleci/config.yml | 4 + .stylelintrc.json | 2 +- README.md | 1 + bundlesize.config.json | 4 + examples/tags-in-searchbox/README.md | 0 examples/tags-in-searchbox/app.tsx | 368 +++++++++++++ examples/tags-in-searchbox/env.ts | 10 + examples/tags-in-searchbox/favicon.png | Bin 0 -> 44137 bytes examples/tags-in-searchbox/index.html | 20 + examples/tags-in-searchbox/package.json | 30 ++ examples/tags-in-searchbox/style.css | 81 +++ .../tags-in-searchbox/types/ProductHit.ts | 19 + .../tags-in-searchbox/types/TagExtraData.ts | 1 + examples/tags-in-searchbox/types/index.ts | 2 + examples/tags-with-hits/README.md | 0 examples/tags-with-hits/app.tsx | 325 +++++++++++ examples/tags-with-hits/env.ts | 10 + examples/tags-with-hits/favicon.png | Bin 0 -> 44137 bytes examples/tags-with-hits/index.html | 196 +++++++ examples/tags-with-hits/package.json | 30 ++ examples/tags-with-hits/style.css | 62 +++ examples/tags-with-hits/types/ProductHit.ts | 19 + examples/tags-with-hits/types/TagExtraData.ts | 1 + examples/tags-with-hits/types/index.ts | 2 + .../src/utils/getNormalizedSources.ts | 4 +- packages/autocomplete-core/src/utils/index.ts | 1 - packages/autocomplete-plugin-tags/README.md | 15 + .../autocomplete-plugin-tags/package.json | 43 ++ .../autocomplete-plugin-tags/rollup.config.js | 5 + .../src/__tests__/createTagsPlugin.test.tsx | 507 ++++++++++++++++++ .../src/createTags.ts | 74 +++ .../src/createTagsPlugin.tsx | 170 ++++++ .../autocomplete-plugin-tags/src/index.ts | 2 + .../autocomplete-plugin-tags/src/theme.scss | 48 ++ .../autocomplete-plugin-tags/src/types/Tag.ts | 11 + .../src/types/index.ts | 1 + .../tsconfig.declaration.json | 3 + .../src}/__tests__/noop.test.ts | 0 packages/autocomplete-shared/src/index.ts | 1 + .../utils => autocomplete-shared/src}/noop.ts | 0 ship.config.js | 1 + 41 files changed, 2068 insertions(+), 5 deletions(-) create mode 100644 examples/tags-in-searchbox/README.md create mode 100644 examples/tags-in-searchbox/app.tsx create mode 100644 examples/tags-in-searchbox/env.ts create mode 100644 examples/tags-in-searchbox/favicon.png create mode 100644 examples/tags-in-searchbox/index.html create mode 100644 examples/tags-in-searchbox/package.json create mode 100644 examples/tags-in-searchbox/style.css create mode 100644 examples/tags-in-searchbox/types/ProductHit.ts create mode 100644 examples/tags-in-searchbox/types/TagExtraData.ts create mode 100644 examples/tags-in-searchbox/types/index.ts create mode 100644 examples/tags-with-hits/README.md create mode 100644 examples/tags-with-hits/app.tsx create mode 100644 examples/tags-with-hits/env.ts create mode 100644 examples/tags-with-hits/favicon.png create mode 100644 examples/tags-with-hits/index.html create mode 100644 examples/tags-with-hits/package.json create mode 100644 examples/tags-with-hits/style.css create mode 100644 examples/tags-with-hits/types/ProductHit.ts create mode 100644 examples/tags-with-hits/types/TagExtraData.ts create mode 100644 examples/tags-with-hits/types/index.ts create mode 100644 packages/autocomplete-plugin-tags/README.md create mode 100644 packages/autocomplete-plugin-tags/package.json create mode 100644 packages/autocomplete-plugin-tags/rollup.config.js create mode 100644 packages/autocomplete-plugin-tags/src/__tests__/createTagsPlugin.test.tsx create mode 100644 packages/autocomplete-plugin-tags/src/createTags.ts create mode 100644 packages/autocomplete-plugin-tags/src/createTagsPlugin.tsx create mode 100644 packages/autocomplete-plugin-tags/src/index.ts create mode 100644 packages/autocomplete-plugin-tags/src/theme.scss create mode 100644 packages/autocomplete-plugin-tags/src/types/Tag.ts create mode 100644 packages/autocomplete-plugin-tags/src/types/index.ts create mode 100644 packages/autocomplete-plugin-tags/tsconfig.declaration.json rename packages/{autocomplete-core/src/utils => autocomplete-shared/src}/__tests__/noop.test.ts (100%) rename packages/{autocomplete-core/src/utils => autocomplete-shared/src}/noop.ts (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index a1b4e14c1..a51f45302 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ aliases: mkdir -p packages/autocomplete-plugin-algolia-insights/dist mkdir -p packages/autocomplete-plugin-recent-searches/dist mkdir -p packages/autocomplete-plugin-query-suggestions/dist + mkdir -p packages/autocomplete-plugin-tags/dist cp -R /tmp/workspace/packages/autocomplete-shared/dist packages/autocomplete-shared cp -R /tmp/workspace/packages/autocomplete-core/dist packages/autocomplete-core @@ -38,6 +39,7 @@ aliases: cp -R /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist packages/autocomplete-plugin-algolia-insights cp -R /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist packages/autocomplete-plugin-recent-searches cp -R /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist packages/autocomplete-plugin-query-suggestions + cp -R /tmp/workspace/packages/autocomplete-plugin-tags/dist packages/autocomplete-plugin-tags defaults: &defaults working_directory: ~/autocomplete @@ -82,6 +84,7 @@ jobs: mkdir -p /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist + mkdir -p /tmp/workspace/packages/autocomplete-plugin-tags/dist cp -R packages/autocomplete-shared/dist /tmp/workspace/packages/autocomplete-shared cp -R packages/autocomplete-core/dist /tmp/workspace/packages/autocomplete-core @@ -90,6 +93,7 @@ jobs: cp -R packages/autocomplete-plugin-algolia-insights/dist /tmp/workspace/packages/autocomplete-plugin-algolia-insights cp -R packages/autocomplete-plugin-recent-searches/dist /tmp/workspace/packages/autocomplete-plugin-recent-searches cp -R packages/autocomplete-plugin-query-suggestions/dist /tmp/workspace/packages/autocomplete-plugin-query-suggestions + cp -R packages/autocomplete-plugin-tags/dist /tmp/workspace/packages/autocomplete-plugin-tags - persist_to_workspace: root: *workspace_root paths: diff --git a/.stylelintrc.json b/.stylelintrc.json index c1461ce35..8620b6e62 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -14,7 +14,7 @@ "order/properties-alphabetical-order": true, "no-descending-specificity": null, "selector-class-pattern": [ - "^aa-(?:[A-Z][a-z]+)+(?:--[a-z]+(?:[A-Z][a-z]+)?)?$" + "^aa(-(?:[A-Z][a-z]+Plugin))?-(?:[A-Z][a-z]+)+(?:--[a-z]+(?:[A-Z][a-z]+)?)?$" ], "prettier/prettier": true, "max-nesting-depth": null, diff --git a/README.md b/README.md index a2d4af9ac..b95bbb2c4 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ You can find more on the [documentation](https://www.algolia.com/doc/ui-librarie | [`autocomplete-plugin-recent-searches`](packages/autocomplete-plugin-recent-searches) | A plugin to add recent searches to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-recent-searches) | | [`autocomplete-plugin-query-suggestions`](packages/autocomplete-plugin-query-suggestions) | A plugin to add query suggestions to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-query-suggestions) | | [`autocomplete-plugin-algolia-insights`](packages/autocomplete-plugin-algolia-insights) | A plugin to add Algolia Insights to Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-algolia-insights) | +| [`autocomplete-plugin-tags`](packages/autocomplete-plugin-tags) | A plugin to manage and display a list of tags in Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags) | | [`autocomplete-preset-algolia`](packages/autocomplete-preset-algolia) | Presets to use Algolia features with Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-preset-algolia) | | [`autocomplete-theme-classic`](packages/autocomplete-theme-classic) | Classic theme for Autocomplete | [Documentation](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic) | diff --git a/bundlesize.config.json b/bundlesize.config.json index a4a3a54cb..556e71985 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -24,6 +24,10 @@ "path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js", "maxSize": "4 kB" }, + { + "path": "packages/autocomplete-plugin-tags/dist/umd/index.production.js", + "maxSize": "2.25 kB" + }, { "path": "packages/autocomplete-theme-classic/dist/theme.min.css", "maxSize": "4.25 kB" diff --git a/examples/tags-in-searchbox/README.md b/examples/tags-in-searchbox/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/examples/tags-in-searchbox/app.tsx b/examples/tags-in-searchbox/app.tsx new file mode 100644 index 000000000..c604dad11 --- /dev/null +++ b/examples/tags-in-searchbox/app.tsx @@ -0,0 +1,368 @@ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + getAlgoliaResults, + getAlgoliaFacets, +} from '@algolia/autocomplete-js'; +import { + AutocompleteInsightsApi, + createAlgoliaInsightsPlugin, +} from '@algolia/autocomplete-plugin-algolia-insights'; +import { createTagsPlugin, Tag } from '@algolia/autocomplete-plugin-tags'; +import algoliasearch from 'algoliasearch'; +import { h, Fragment, render } from 'preact'; +import groupBy from 'ramda/src/groupBy'; +import insightsClient from 'search-insights'; + +import '@algolia/autocomplete-theme-classic'; + +import { ProductHit, TagExtraData } from './types'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +// @ts-expect-error type error in search-insights +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + +const tagsPlugin = createTagsPlugin({ + getTagsSubscribers() { + return [ + { + sourceId: 'brands', + getTag({ item }) { + return item; + }, + }, + { + sourceId: 'categories', + getTag({ item }) { + return item; + }, + }, + ]; + }, + transformSource() { + return undefined; + }, + onChange({ tags }) { + requestAnimationFrame(() => { + const container = document.querySelector('.aa-InputWrapperPrefix'); + const oldTagsContainer = document.querySelector('.aa-Tags'); + + const tagsContainer = document.createElement('div'); + tagsContainer.classList.add('aa-Tags'); + + render( +
+ {tags.map((tag) => ( + + ))} +
, + tagsContainer + ); + + if (oldTagsContainer) { + container.replaceChild(tagsContainer, oldTagsContainer); + } else { + container.appendChild(tagsContainer); + } + }); + }, +}); + +type TagItemProps = Tag; + +function TagItem({ label, remove }: TagItemProps) { + return ( +
+ {label} + +
+ ); +} + +autocomplete>({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + plugins: [algoliaInsightsPlugin, tagsPlugin], + detachedMediaQuery: 'none', + getSources({ query, state }) { + const tagsByFacet = groupBy>( + (tag) => tag.facet, + state.context.tagsPlugin.tags + ); + + return [ + { + sourceId: 'brands', + onSelect({ item, state, setQuery }) { + if ( + item.label.toLowerCase().includes(state.query.toLowerCase().trim()) + ) { + setQuery(''); + } + }, + getItems({ query }) { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'brand', + params: { + facetQuery: query, + maxFacetHits: 3, + filters: mapToAlgoliaNegativeFilters( + state.context.tagsPlugin.tags, + ['brand'] + ), + }, + }, + ], + transformResponse({ facetHits }) { + return facetHits[0].map((hit) => ({ ...hit, facet: 'brand' })); + }, + }); + }, + templates: { + header() { + return ( + + Brands +
+ + ); + }, + item({ item, components }) { + return ( +
+
+
+
+ Filter on{' '} + +
+
+
+
+ +
+
+ ); + }, + noResults() { + return 'No brands for this query.'; + }, + }, + }, + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + clickAnalytics: true, + attributesToSnippet: ['name:10'], + snippetEllipsisText: '…', + filters: mapToAlgoliaFilters(tagsByFacet), + }, + }, + ], + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ( + + ); + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; + insights: AutocompleteInsightsApi; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, insights, components }: ProductItemProps) { + return ( + +
+
+ {hit.name} +
+
+
+ +
+
+ From {hit.brand} in{' '} + {hit.categories[0]} +
+ {hit.rating > 0 && ( +
+
+ {Array.from({ length: 5 }, (_value, index) => { + const isFilled = hit.rating >= index + 1; + + return ( + + + + ); + })} +
+
+ )} +
+ ${hit.price.toLocaleString()} +
+
+
+
+ + +
+
+ ); +} + +const searchInput: HTMLInputElement = document.querySelector( + '.aa-Autocomplete .aa-Input' +); + +searchInput.addEventListener('keydown', (event) => { + if ( + event.key === 'Backspace' && + searchInput.selectionStart === 0 && + searchInput.selectionEnd === 0 + ) { + const newTags = tagsPlugin.data.tags.slice(0, -1); + tagsPlugin.data.setTags(newTags); + } +}); + +function mapToAlgoliaFilters( + tagsByFacet: Record>>, + operator = 'AND' +) { + return Object.keys(tagsByFacet) + .map((facet) => { + return `(${tagsByFacet[facet] + .map(({ label }) => `${facet}:"${label}"`) + .join(' OR ')})`; + }) + .join(` ${operator} `); +} + +function mapToAlgoliaNegativeFilters( + tags: Array>, + facetsToNegate: string[], + operator = 'AND' +) { + return tags + .map(({ label, facet }) => { + const filter = `${facet}:"${label}"`; + + return facetsToNegate.includes(facet) && `NOT ${filter}`; + }) + .filter(Boolean) + .join(` ${operator} `); +} diff --git a/examples/tags-in-searchbox/env.ts b/examples/tags-in-searchbox/env.ts new file mode 100644 index 000000000..6eef24529 --- /dev/null +++ b/examples/tags-in-searchbox/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/tags-in-searchbox/favicon.png b/examples/tags-in-searchbox/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..084fdfdfc2dff34a510e79b2f8ee8c19a7db96b4 GIT binary patch literal 44137 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 + + + + + + + + Tags in the searchbox | Autocomplete + + + +
+
+
+ + + + + diff --git a/examples/tags-in-searchbox/package.json b/examples/tags-in-searchbox/package.json new file mode 100644 index 000000000..b140ec67c --- /dev/null +++ b/examples/tags-in-searchbox/package.json @@ -0,0 +1,30 @@ +{ + "name": "@algolia/autocomplete-example-tags-in-searchbox", + "description": "Autocomplete example with Tags in the searchbox", + "version": "1.3.0", + "private": true, + "license": "MIT", + "scripts": { + "build": "parcel build index.html", + "start": "parcel index.html" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.3.0", + "@algolia/autocomplete-plugin-algolia-insights": "1.3.0", + "@algolia/autocomplete-plugin-tags": "1.3.0", + "@algolia/autocomplete-theme-classic": "1.3.0", + "@algolia/client-search": "4.9.1", + "algoliasearch": "4.9.1", + "preact": "10.5.13", + "ramda": "0.27.1", + "search-insights": "1.7.1" + }, + "devDependencies": { + "parcel": "2.0.0-beta.2" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/tags-in-searchbox/style.css b/examples/tags-in-searchbox/style.css new file mode 100644 index 000000000..4d01551be --- /dev/null +++ b/examples/tags-in-searchbox/style.css @@ -0,0 +1,81 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +.aa-Tags { + margin-right: 8px; +} + +.aa-Tags:empty { + display: none; +} + +.aa-TagsList { + display: flex; + margin: 0 calc((var(--aa-spacing) / 5) * -1); +} + +.aa-Tag { + background-color: rgba( + var(--aa-selected-color-rgb), + var(--aa-selected-color-alpha) + ); + align-items: center; + display: flex; + margin: 0 calc(var(--aa-spacing) / 5); + padding-left: var(--aa-spacing-half); + border-radius: calc(var(--aa-spacing) / 4); +} + +.aa-Tag:hover, +.aa-Tag:focus { + background-color: rgba(var(--aa-selected-color-rgb), 0.55); +} + +.aa-TagLabel { + font-size: 0.8em; +} + +.aa-TagRemoveButton { + cursor: pointer; + padding: 0; + border: 0; + background: none; +} + +.aa-TagRemoveButton svg { + color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); + margin: 0; + margin: calc(var(--aa-spacing) / 2.5); + stroke-width: var(--aa-icon-stroke-width); + width: calc(var(--aa-action-icon-size) / 1.5); +} + +.aa-TagRemoveButton:hover svg, +.aa-TagRemoveButton:focus svg { + color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); +} + +@media (hover: none) and (pointer: coarse) { + .aa-TagRemoveButton:hover, + .aa-TagRemoveButton:focus { + color: inherit; + } +} diff --git a/examples/tags-in-searchbox/types/ProductHit.ts b/examples/tags-in-searchbox/types/ProductHit.ts new file mode 100644 index 000000000..349301e3c --- /dev/null +++ b/examples/tags-in-searchbox/types/ProductHit.ts @@ -0,0 +1,19 @@ +import { Hit } from '@algolia/client-search'; + +type ProductRecord = { + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + url: string; +}; + +type WithAutocompleteAnalytics = THit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; + +export type ProductHit = WithAutocompleteAnalytics>; diff --git a/examples/tags-in-searchbox/types/TagExtraData.ts b/examples/tags-in-searchbox/types/TagExtraData.ts new file mode 100644 index 000000000..769e47dd9 --- /dev/null +++ b/examples/tags-in-searchbox/types/TagExtraData.ts @@ -0,0 +1 @@ +export type TagExtraData = { facet: string }; diff --git a/examples/tags-in-searchbox/types/index.ts b/examples/tags-in-searchbox/types/index.ts new file mode 100644 index 000000000..1344ae054 --- /dev/null +++ b/examples/tags-in-searchbox/types/index.ts @@ -0,0 +1,2 @@ +export * from './ProductHit'; +export * from './TagExtraData'; diff --git a/examples/tags-with-hits/README.md b/examples/tags-with-hits/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/examples/tags-with-hits/app.tsx b/examples/tags-with-hits/app.tsx new file mode 100644 index 000000000..6d198def0 --- /dev/null +++ b/examples/tags-with-hits/app.tsx @@ -0,0 +1,325 @@ +/** @jsx h */ +import { + autocomplete, + AutocompleteComponents, + getAlgoliaResults, + getAlgoliaFacets, +} from '@algolia/autocomplete-js'; +import { + AutocompleteInsightsApi, + createAlgoliaInsightsPlugin, +} from '@algolia/autocomplete-plugin-algolia-insights'; +import { createTagsPlugin, Tag } from '@algolia/autocomplete-plugin-tags'; +import algoliasearch from 'algoliasearch'; +import { h, Fragment } from 'preact'; +import groupBy from 'ramda/src/groupBy'; +import insightsClient from 'search-insights'; + +import '@algolia/autocomplete-theme-classic'; +import '@algolia/autocomplete-plugin-tags/dist/theme.min.css'; + +import { ProductHit, TagExtraData } from './types'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +// @ts-expect-error type error in search-insights +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + +const tagsPlugin = createTagsPlugin({ + getTagsSubscribers() { + return [ + { + sourceId: 'brands', + getTag({ item }) { + return item; + }, + }, + { + sourceId: 'categories', + getTag({ item }) { + return item; + }, + }, + ]; + }, +}); + +const categoriesSelect = document.getElementById( + 'categories' +) as HTMLSelectElement; + +autocomplete>({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + plugins: [algoliaInsightsPlugin, tagsPlugin], + detachedMediaQuery: 'none', + onStateChange({ state }) { + const tags = state.context.tagsPlugin?.tags || []; + const currentCategory = tags.find(({ facet }) => facet === 'categories'); + + categoriesSelect.value = currentCategory?.label || ''; + }, + getSources({ query, state }) { + const tagsByFacet = groupBy>( + (tag) => tag.facet, + state.context.tagsPlugin.tags + ); + + return [ + { + sourceId: 'brands', + onSelect({ item, state, setQuery }) { + if (item.label.toLowerCase().includes(state.query.toLowerCase())) { + setQuery(''); + } + }, + getItems({ query }) { + return getAlgoliaFacets({ + searchClient, + queries: [ + { + indexName: 'instant_search', + facet: 'brand', + params: { + facetQuery: query, + maxFacetHits: 3, + filters: mapToAlgoliaNegativeFilters( + state.context.tagsPlugin.tags, + ['brand'] + ), + }, + }, + ], + transformResponse({ facetHits }) { + return facetHits[0].map((hit) => ({ ...hit, facet: 'brand' })); + }, + }); + }, + templates: { + header() { + return ( + + Brands +
+ + ); + }, + item({ item, components }) { + return ( +
+
+
+
+ Filter on{' '} + +
+
+
+
+ +
+
+ ); + }, + noResults() { + return 'No brands for this query.'; + }, + }, + }, + { + sourceId: 'products', + getItems() { + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: 'instant_search', + query, + params: { + clickAnalytics: true, + attributesToSnippet: ['name:10'], + snippetEllipsisText: '…', + filters: mapToAlgoliaFilters(tagsByFacet), + }, + }, + ], + }); + }, + templates: { + header() { + return ( + + Products +
+ + ); + }, + item({ item, components }) { + return ( + + ); + }, + noResults() { + return 'No products for this query.'; + }, + }, + }, + ]; + }, +}); + +type ProductItemProps = { + hit: ProductHit; + insights: AutocompleteInsightsApi; + components: AutocompleteComponents; +}; + +function ProductItem({ hit, insights, components }: ProductItemProps) { + return ( + +
+
+ {hit.name} +
+
+
+ +
+
+ From {hit.brand} in{' '} + {hit.categories[0]} +
+ {hit.rating > 0 && ( +
+
+ {Array.from({ length: 5 }, (_value, index) => { + const isFilled = hit.rating >= index + 1; + + return ( + + + + ); + })} +
+
+ )} +
+ ${hit.price.toLocaleString()} +
+
+
+
+ + +
+
+ ); +} + +function mapToAlgoliaFilters( + tagsByFacet: Record>>, + operator = 'AND' +) { + return Object.keys(tagsByFacet) + .map((facet) => { + return `(${tagsByFacet[facet] + .map(({ label }) => `${facet}:"${label}"`) + .join(' OR ')})`; + }) + .join(` ${operator} `); +} + +function mapToAlgoliaNegativeFilters( + tags: Array>, + facetsToNegate: string[], + operator = 'AND' +) { + return tags + .map(({ label, facet }) => { + const filter = `${facet}:"${label}"`; + + return facetsToNegate.includes(facet) && `NOT ${filter}`; + }) + .filter(Boolean) + .join(` ${operator} `); +} + +categoriesSelect.addEventListener('change', (event) => { + const value = (event.target as HTMLSelectElement).value; + const tags = tagsPlugin.data.tags.filter((tag) => tag.facet !== 'categories'); + + if (value) { + tagsPlugin.data.setTags([ + ...tags, + { + label: value, + facet: 'categories', + }, + ]); + } else { + tagsPlugin.data.setTags(tags); + } +}); diff --git a/examples/tags-with-hits/env.ts b/examples/tags-with-hits/env.ts new file mode 100644 index 000000000..6eef24529 --- /dev/null +++ b/examples/tags-with-hits/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config. We therefore need to manually override the constants +// in the app, as well as the React pragmas. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/tags-with-hits/favicon.png b/examples/tags-with-hits/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..084fdfdfc2dff34a510e79b2f8ee8c19a7db96b4 GIT binary patch literal 44137 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 + + + + + + + + Tags with hits | Autocomplete + + + +
+
+ +
+
+
+ + + + + diff --git a/examples/tags-with-hits/package.json b/examples/tags-with-hits/package.json new file mode 100644 index 000000000..fbee343c1 --- /dev/null +++ b/examples/tags-with-hits/package.json @@ -0,0 +1,30 @@ +{ + "name": "@algolia/autocomplete-example-tags-with-hits", + "description": "Autocomplete example with Tags and hits", + "version": "1.3.0", + "private": true, + "license": "MIT", + "scripts": { + "build": "parcel build index.html", + "start": "parcel index.html" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.3.0", + "@algolia/autocomplete-plugin-algolia-insights": "1.3.0", + "@algolia/autocomplete-plugin-tags": "1.3.0", + "@algolia/autocomplete-theme-classic": "1.3.0", + "@algolia/client-search": "4.9.1", + "algoliasearch": "4.9.1", + "preact": "10.5.13", + "ramda": "0.27.1", + "search-insights": "1.7.1" + }, + "devDependencies": { + "parcel": "2.0.0-beta.2" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/tags-with-hits/style.css b/examples/tags-with-hits/style.css new file mode 100644 index 000000000..24bdf8b45 --- /dev/null +++ b/examples/tags-with-hits/style.css @@ -0,0 +1,62 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} + +#autocomplete { + flex-shrink: 0; + flex-grow: 1; +} + +.aa-SearchBoxWrapper { + display: flex; +} + +.aa-SearchBoxWrapper .aa-Select { + flex-grow: 0; + width: 200px; + padding: 0 calc(var(--aa-spacing) * 0.75 - 1px); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + cursor: pointer; +} + +.aa-Autocomplete .aa-Form { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; +} + +.aa-Select .aa-InputWrapper { + display: flex; +} + +.aa-Select select { + cursor: pointer; + text-overflow: ellipsis; +} + +.aa-SelectIcon { + order: 4; +} + +.aa-SelectIcon svg { + width: var(--aa-spacing); + height: var(--aa-spacing); +} diff --git a/examples/tags-with-hits/types/ProductHit.ts b/examples/tags-with-hits/types/ProductHit.ts new file mode 100644 index 000000000..349301e3c --- /dev/null +++ b/examples/tags-with-hits/types/ProductHit.ts @@ -0,0 +1,19 @@ +import { Hit } from '@algolia/client-search'; + +type ProductRecord = { + brand: string; + categories: string[]; + description: string; + image: string; + name: string; + price: number; + rating: number; + url: string; +}; + +type WithAutocompleteAnalytics = THit & { + __autocomplete_indexName: string; + __autocomplete_queryID: string; +}; + +export type ProductHit = WithAutocompleteAnalytics>; diff --git a/examples/tags-with-hits/types/TagExtraData.ts b/examples/tags-with-hits/types/TagExtraData.ts new file mode 100644 index 000000000..769e47dd9 --- /dev/null +++ b/examples/tags-with-hits/types/TagExtraData.ts @@ -0,0 +1 @@ +export type TagExtraData = { facet: string }; diff --git a/examples/tags-with-hits/types/index.ts b/examples/tags-with-hits/types/index.ts new file mode 100644 index 000000000..1344ae054 --- /dev/null +++ b/examples/tags-with-hits/types/index.ts @@ -0,0 +1,2 @@ +export * from './ProductHit'; +export * from './TagExtraData'; diff --git a/packages/autocomplete-core/src/utils/getNormalizedSources.ts b/packages/autocomplete-core/src/utils/getNormalizedSources.ts index 52b63491b..56148d5ff 100644 --- a/packages/autocomplete-core/src/utils/getNormalizedSources.ts +++ b/packages/autocomplete-core/src/utils/getNormalizedSources.ts @@ -1,4 +1,4 @@ -import { invariant, decycle } from '@algolia/autocomplete-shared'; +import { invariant, decycle, noop } from '@algolia/autocomplete-shared'; import { AutocompleteSource, @@ -9,8 +9,6 @@ import { InternalGetSources, } from '../types'; -import { noop } from './noop'; - export function getNormalizedSources( getSources: GetSources, params: GetSourcesParams diff --git a/packages/autocomplete-core/src/utils/index.ts b/packages/autocomplete-core/src/utils/index.ts index 78bfba092..c4245e292 100644 --- a/packages/autocomplete-core/src/utils/index.ts +++ b/packages/autocomplete-core/src/utils/index.ts @@ -4,4 +4,3 @@ export * from './getNormalizedSources'; export * from './getActiveItem'; export * from './isOrContainsNode'; export * from './mapToAlgoliaResponse'; -export * from './noop'; diff --git a/packages/autocomplete-plugin-tags/README.md b/packages/autocomplete-plugin-tags/README.md new file mode 100644 index 000000000..4fc6d3c5f --- /dev/null +++ b/packages/autocomplete-plugin-tags/README.md @@ -0,0 +1,15 @@ +# @algolia/autocomplete-plugin-tags + +The Tags plugin lets you manage and display a list of tags in your autocomplete. + +## Installation + +```sh +yarn add @algolia/autocomplete-plugin-tags +# or +npm install @algolia/autocomplete-plugin-tags +``` + +## Documentation + +See [**Documentation**](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags). diff --git a/packages/autocomplete-plugin-tags/package.json b/packages/autocomplete-plugin-tags/package.json new file mode 100644 index 000000000..bd5646556 --- /dev/null +++ b/packages/autocomplete-plugin-tags/package.json @@ -0,0 +1,43 @@ +{ + "name": "@algolia/autocomplete-plugin-tags", + "description": "A plugin to manage and display a list of tags in Algolia Autocomplete.", + "version": "1.3.0", + "license": "MIT", + "homepage": "https://github.com/algolia/autocomplete", + "repository": "algolia/autocomplete", + "author": { + "name": "Algolia, Inc.", + "url": "https://www.algolia.com" + }, + "source": "src/index.ts", + "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", + "main": "dist/umd/index.production.js", + "umd:main": "dist/umd/index.production.js", + "unpkg": "dist/umd/index.production.js", + "jsdelivr": "dist/umd/index.production.js", + "sideEffects": [ + "*.css" + ], + "files": [ + "dist/" + ], + "scripts": { + "build:clean": "rm -rf ./dist", + "build:esm": "babel src --root-mode upward --extensions '.ts,.tsx' --out-dir dist/esm --ignore '**/*/__tests__/'", + "build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm", + "build:umd": "rollup --config", + "build:css": "yarn build:css:minified && yarn build:css:unminified", + "build:css:minified": "MINIFIED=TRUE node ../../scripts/buildCss.mjs src/theme.scss dist/theme.min.css", + "build:css:unminified": "node ../../scripts/buildCss.mjs src/theme.scss dist/theme.css", + "build": "rm -rf ./dist && yarn build:umd && yarn build:esm && yarn build:types && yarn build:css", + "on:change": "concurrently \"yarn build:esm\" \"yarn build:types\" \"yarn build:css\"", + "prepare": "yarn run build:esm && yarn build:types && yarn build:css", + "watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\"" + }, + "devDependencies": { + "@algolia/autocomplete-core": "1.3.0", + "@algolia/autocomplete-js": "1.3.0", + "@algolia/autocomplete-shared": "1.3.0" + } +} diff --git a/packages/autocomplete-plugin-tags/rollup.config.js b/packages/autocomplete-plugin-tags/rollup.config.js new file mode 100644 index 000000000..099ce0e3a --- /dev/null +++ b/packages/autocomplete-plugin-tags/rollup.config.js @@ -0,0 +1,5 @@ +import { createRollupConfigs } from '../../scripts/rollup/config'; + +import pkg from './package.json'; + +export default createRollupConfigs({ pkg }); diff --git a/packages/autocomplete-plugin-tags/src/__tests__/createTagsPlugin.test.tsx b/packages/autocomplete-plugin-tags/src/__tests__/createTagsPlugin.test.tsx new file mode 100644 index 000000000..6db6a09f7 --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/__tests__/createTagsPlugin.test.tsx @@ -0,0 +1,507 @@ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { fireEvent, waitFor, within } from '@testing-library/dom'; +import userEvent from '@testing-library/user-event'; +import { h, render, createElement, Fragment } from 'preact'; + +import { createTagsPlugin } from '../createTagsPlugin'; + +beforeEach(() => { + document.body.innerHTML = ''; +}); + +describe('createTagsPlugin', () => { + test('adds a tags source', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + }); + + autocomplete({ + container, + panelContainer, + plugins: [tagsPlugin], + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ) + .getAllByRole('option') + .map((option) => option.children) + ).toMatchInlineSnapshot(` + Array [ + HTMLCollection [ +
+ + iPhone 12 + + +
, + ], + ] + `); + }); + }); + + test('transforms the tags source', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + transformSource({ source }) { + return { + ...source, + templates: { + item({ item }) { + return item.label; + }, + }, + }; + }, + }); + + autocomplete({ + container, + panelContainer, + plugins: [tagsPlugin], + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ) + .getAllByRole('option') + .map((option) => option.textContent) + ).toEqual(['iPhone 12']); + }); + }); + + test('fully customizes tags rendering', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + transformSource() { + return null; + }, + }); + + autocomplete({ + container, + panelContainer, + renderer: { createElement, Fragment }, + getSources() { + return [ + { + sourceId: 'filters', + getItems() { + return [ + { + label: 'iPhone 12', + }, + { + label: 'Samsung Galaxy S', + }, + ]; + }, + templates: { + item({ item }) { + return item.label as string; + }, + }, + }, + ]; + }, + plugins: [tagsPlugin], + render({ sections, state }, root) { + render( +
+ +

Filters

+
    + {state.context.tagsPlugin.tags.map((tag) => ( +
  • tag.remove()} + > + Filter: {tag.label} +
  • + ))} +
+

Suggestions

+
    +
  • + +
  • +
+
{sections}
+
, + root + ); + }, + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + document.querySelector('.aa-Panel') + ).toBeInTheDocument(); + }); + + await waitFor(() => { + expect( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ).toBeNull(); + }); + + await waitFor(() => { + expect( + within(panelContainer).getByRole('listitem', { + name: 'Filter: iPhone 12', + }) + ).toBeInTheDocument(); + }); + + userEvent.click( + within(panelContainer).getByRole('button', { + name: 'Suggestion: Samsung Galaxy S', + }) + ); + + await waitFor(() => { + expect( + within(panelContainer).getByRole('listitem', { + name: 'Filter: Samsung Galaxy S', + }) + ).toBeInTheDocument(); + }); + + userEvent.click( + within(panelContainer).getByRole('button', { name: 'Clear all' }) + ); + + await waitFor(() => { + expect( + within(panelContainer).queryByRole('listitem', { + name: 'Filter: iPhone 12', + }) + ).toBeNull(); + }); + }); + + test('returns the tags', () => { + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + }); + + expect(tagsPlugin.data.tags).toEqual([ + expect.objectContaining({ label: 'iPhone 12' }), + ]); + }); + + test('adds tags', () => { + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + }); + + tagsPlugin.data.addTags([{ label: 'Samsung Galaxy S' }]); + + expect(tagsPlugin.data.tags).toEqual([ + expect.objectContaining({ label: 'iPhone 12' }), + expect.objectContaining({ label: 'Samsung Galaxy S' }), + ]); + }); + + test('sets tags', () => { + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + }); + + tagsPlugin.data.setTags([{ label: 'Samsung Galaxy S' }]); + + expect(tagsPlugin.data.tags).toEqual([ + expect.objectContaining({ label: 'Samsung Galaxy S' }), + ]); + }); + + test('runs a callback when tags change', () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const onChange = jest.fn(); + + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + onChange, + }); + + autocomplete({ + container, + panelContainer, + plugins: [tagsPlugin], + }); + + tagsPlugin.data.addTags([{ label: 'Samsung Galaxy S' }]); + + expect(onChange).toHaveBeenNthCalledWith(1, { + onActive: expect.any(Function), + onSelect: expect.any(Function), + refresh: expect.any(Function), + setActiveItemId: expect.any(Function), + setCollections: expect.any(Function), + setContext: expect.any(Function), + setIsOpen: expect.any(Function), + setQuery: expect.any(Function), + setStatus: expect.any(Function), + prevTags: [ + { + label: 'iPhone 12', + remove: expect.any(Function), + }, + ], + tags: [ + { + label: 'iPhone 12', + remove: expect.any(Function), + }, + { + label: 'Samsung Galaxy S', + remove: expect.any(Function), + }, + ], + }); + }); + + test('adds a tag on subscriber item select then removes a tag on tags source item select', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const tagsPlugin = createTagsPlugin({ + getTagsSubscribers() { + return [ + { + sourceId: 'filters', + getTag({ item }) { + return { + label: `Filter: ${item.label}`, + }; + }, + }, + ]; + }, + }); + + autocomplete({ + container, + panelContainer, + getSources() { + return [ + { + sourceId: 'filters', + getItems() { + return [ + { + label: 'iPhone 12', + }, + { + label: 'Samsung Galaxy S', + }, + ]; + }, + templates: { + item({ item }) { + return item.label as string; + }, + }, + }, + ]; + }, + plugins: [tagsPlugin], + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + document.querySelector('.aa-Panel') + ).toBeInTheDocument(); + }); + + userEvent.click( + within( + panelContainer.querySelector('[data-autocomplete-source-id="filters"]') + ).getByRole('option', { name: 'iPhone 12' }) + ); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toEqual(['Filter: iPhone 12']); + }); + + userEvent.click( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ).getByRole('option', { name: 'Filter: iPhone 12' }) + ); + + await waitFor(() => { + expect( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ).toBeNull(); + }); + }); + + test('keeps the panel open on tags update', async () => { + const container = document.createElement('div'); + const panelContainer = document.createElement('div'); + + document.body.appendChild(panelContainer); + + const tagsPlugin = createTagsPlugin({ + initialTags: [ + { + label: 'iPhone 12', + }, + ], + }); + + autocomplete({ + container, + panelContainer, + plugins: [tagsPlugin], + }); + + const input = container.querySelector('.aa-Input'); + + fireEvent.input(input, { target: { value: 'a' } }); + + await waitFor(() => { + expect( + document.querySelector('.aa-Panel') + ).toBeInTheDocument(); + }); + + tagsPlugin.data.addTags([{ label: 'Samsung Galaxy S' }]); + + await waitFor(() => { + expect( + within( + panelContainer.querySelector( + '[data-autocomplete-source-id="tagsPlugin"]' + ) + ) + .getAllByRole('option') + .map((node) => node.textContent) + ).toEqual(['iPhone 12', 'Samsung Galaxy S']); + }); + }); +}); diff --git a/packages/autocomplete-plugin-tags/src/createTags.ts b/packages/autocomplete-plugin-tags/src/createTags.ts new file mode 100644 index 000000000..72a4b9d0b --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/createTags.ts @@ -0,0 +1,74 @@ +import { BaseItem } from '@algolia/autocomplete-core'; +import { createRef } from '@algolia/autocomplete-shared'; + +import { CreateTagsPluginParams } from './createTagsPlugin'; +import { DefaultTagType, BaseTag, Tag } from './types'; + +export type OnTagsChangeParams = { + prevTags: Array>; + tags: Array>; +}; + +type OnTagsChangeListener = ( + params: OnTagsChangeParams +) => void; + +type CreateTagsParams< + TItem extends BaseItem, + TTag extends DefaultTagType = DefaultTagType +> = Pick, 'initialTags'>; + +export function createTags< + TItem extends BaseItem, + TTag extends DefaultTagType = DefaultTagType +>({ initialTags = [] }: CreateTagsParams) { + const tagsRef = createRef(mapToTags(initialTags)); + const onChangeListeners: Array> = []; + + function mapToTags( + baseTags: Array> + ): Array> { + return baseTags.map((baseTag) => { + const tag = { + ...baseTag, + remove() { + const prevTags = tagsRef.current.slice(); + + tagsRef.current = tagsRef.current.filter( + (tagRef) => tag !== ((tagRef as unknown) as Tag) + ); + onChangeListeners.forEach((listener) => + listener({ prevTags, tags: tagsRef.current }) + ); + }, + }; + + return tag; + }); + } + + return { + get() { + return tagsRef.current; + }, + set(baseTags: Array>) { + const prevTags = tagsRef.current.slice(); + + tagsRef.current = mapToTags(baseTags); + onChangeListeners.forEach((listener) => + listener({ prevTags, tags: tagsRef.current }) + ); + }, + add(baseTags: Array>) { + const prevTags = tagsRef.current.slice(); + + tagsRef.current.push(...mapToTags(baseTags)); + onChangeListeners.forEach((listener) => + listener({ prevTags, tags: tagsRef.current }) + ); + }, + onChange(listener: OnTagsChangeListener) { + onChangeListeners.push(listener); + }, + }; +} diff --git a/packages/autocomplete-plugin-tags/src/createTagsPlugin.tsx b/packages/autocomplete-plugin-tags/src/createTagsPlugin.tsx new file mode 100644 index 000000000..0bf1c401b --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/createTagsPlugin.tsx @@ -0,0 +1,170 @@ +/** @jsx createElement */ +import { BaseItem, PluginSubscribeParams } from '@algolia/autocomplete-core'; +import { + AutocompletePlugin, + AutocompleteSource, + AutocompleteState, +} from '@algolia/autocomplete-js'; +import { noop } from '@algolia/autocomplete-shared'; + +import { createTags, OnTagsChangeParams } from './createTags'; +import type { DefaultTagType, BaseTag, Tag } from './types'; + +type OnChangeParams< + TTag extends DefaultTagType = DefaultTagType +> = PluginSubscribeParams & OnTagsChangeParams; + +type GetTagParams = { item: TItem }; + +type TagsPluginData = { + /** + * Returns the current list of tags. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-tags + */ + tags: Array>; + /** + * Adds tags to the list. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-addtags + */ + addTags: (tags: Array>) => void; + /** + * Sets the list of tags. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-settags + */ + setTags: (tags: Array>) => void; +}; + +export type TagsApi< + TTag extends DefaultTagType = DefaultTagType +> = TagsPluginData; + +export type CreateTagsPluginParams< + TItem extends BaseItem, + TTag extends DefaultTagType +> = { + /** + * A set of initial tags to pass to the plugin. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-initialtags + */ + initialTags?: Array>; + /** + * A function to specify what sources the plugin should subscribe to. The plugin adds a tag when selecting an item from these sources. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-gettagssubscribers + */ + getTagsSubscribers?(): Array<{ + sourceId: string; + getTag(params: GetTagParams): BaseTag; + }>; + /** + * A function to transform the returned tags source. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-transformsource + */ + transformSource?(params: { + source: AutocompleteSource>; + state: AutocompleteState>; + }): AutocompleteSource> | undefined; + /** + * A function called when the list of tags changes. + * + * @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-plugin-tags/createtagsplugin/#param-onchange + */ + onChange?(params: OnChangeParams): void; +}; + +export function createTagsPlugin< + TItem extends BaseItem, + TTag extends DefaultTagType = DefaultTagType +>({ + initialTags = [], + getTagsSubscribers = () => [], + transformSource = ({ source }) => source, + onChange = noop, +}: CreateTagsPluginParams = {}): AutocompletePlugin< + Tag, + TagsPluginData +> { + const tags = createTags({ initialTags }); + const tagsApi = { setTags: tags.set, addTags: tags.add }; + + return { + subscribe(params) { + const { setContext, onSelect, setIsOpen, refresh } = params; + const subscribers = getTagsSubscribers(); + + setContext({ tagsPlugin: { ...tagsApi, tags: tags.get() } }); + + onSelect(({ source, item }) => { + const subscriber = subscribers.find( + ({ sourceId }) => sourceId === source.sourceId + ); + + if (subscriber) { + tags.add([subscriber.getTag({ item })]); + } + }); + + tags.onChange(({ prevTags }) => { + setContext({ + tagsPlugin: { ...tagsApi, tags: tags.get() }, + }); + + setIsOpen(true); + onChange({ ...params, prevTags, tags: tags.get() }); + refresh(); + }); + }, + getSources({ state }) { + return [ + transformSource({ + source: { + sourceId: 'tagsPlugin', + getItems() { + return tags.get(); + }, + onSelect({ item }) { + item.remove(); + }, + templates: { + item({ item, createElement }) { + return ( +
+ {item.label} + +
+ ); + }, + }, + }, + state: state as AutocompleteState>, + }), + ]; + }, + data: { + ...tagsApi, + get tags() { + return tags.get(); + }, + }, + }; +} diff --git a/packages/autocomplete-plugin-tags/src/index.ts b/packages/autocomplete-plugin-tags/src/index.ts new file mode 100644 index 000000000..63924f34c --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './createTagsPlugin'; diff --git a/packages/autocomplete-plugin-tags/src/theme.scss b/packages/autocomplete-plugin-tags/src/theme.scss new file mode 100644 index 000000000..64a5404a2 --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/theme.scss @@ -0,0 +1,48 @@ +[data-autocomplete-source-id='tagsPlugin'] { + margin-bottom: var(--aa-spacing-half); + .aa-List { + display: flex; + flex-wrap: wrap; + margin: calc(var(--aa-spacing-half) / 2 * -1); + } + .aa-Item { + background-color: rgba( + var(--aa-selected-color-rgb), + var(--aa-selected-color-alpha) + ); + margin: calc(var(--aa-spacing-half) / 2); + min-height: auto; + &[aria-selected='true'] { + background-color: rgba(var(--aa-selected-color-rgb), 0.55); + } + } +} + +.aa-TagsPlugin-Tag { + align-items: center; + display: flex; + padding-left: var(--aa-spacing-half); +} + +.aa-TagsPlugin-TagLabel { + font-size: 0.8em; +} + +.aa-TagsPlugin-RemoveButton { + cursor: pointer; + svg { + color: rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)); + margin: calc(var(--aa-spacing) / 3); + stroke-width: var(--aa-icon-stroke-width); + width: calc(var(--aa-action-icon-size) / 1.5); + } + &:hover, + &:focus { + svg { + color: rgba(var(--aa-text-color-rgb), var(--aa-text-color-alpha)); + } + @media (hover: none) and (pointer: coarse) { + color: inherit; + } + } +} diff --git a/packages/autocomplete-plugin-tags/src/types/Tag.ts b/packages/autocomplete-plugin-tags/src/types/Tag.ts new file mode 100644 index 000000000..9ba909e27 --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/types/Tag.ts @@ -0,0 +1,11 @@ +export type DefaultTagType = Record; + +export type BaseTag = TTag & { + label: string; +}; + +export type Tag< + TTag extends DefaultTagType = DefaultTagType +> = BaseTag & { + remove: () => void; +}; diff --git a/packages/autocomplete-plugin-tags/src/types/index.ts b/packages/autocomplete-plugin-tags/src/types/index.ts new file mode 100644 index 000000000..9790fcbf1 --- /dev/null +++ b/packages/autocomplete-plugin-tags/src/types/index.ts @@ -0,0 +1 @@ +export * from './Tag'; diff --git a/packages/autocomplete-plugin-tags/tsconfig.declaration.json b/packages/autocomplete-plugin-tags/tsconfig.declaration.json new file mode 100644 index 000000000..1e0c6449f --- /dev/null +++ b/packages/autocomplete-plugin-tags/tsconfig.declaration.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.declaration" +} diff --git a/packages/autocomplete-core/src/utils/__tests__/noop.test.ts b/packages/autocomplete-shared/src/__tests__/noop.test.ts similarity index 100% rename from packages/autocomplete-core/src/utils/__tests__/noop.test.ts rename to packages/autocomplete-shared/src/__tests__/noop.test.ts diff --git a/packages/autocomplete-shared/src/index.ts b/packages/autocomplete-shared/src/index.ts index cdcd3b7ff..8ed4431a8 100644 --- a/packages/autocomplete-shared/src/index.ts +++ b/packages/autocomplete-shared/src/index.ts @@ -8,4 +8,5 @@ export * from './getItemsCount'; export * from './invariant'; export * from './isEqual'; export * from './MaybePromise'; +export * from './noop'; export * from './warn'; diff --git a/packages/autocomplete-core/src/utils/noop.ts b/packages/autocomplete-shared/src/noop.ts similarity index 100% rename from packages/autocomplete-core/src/utils/noop.ts rename to packages/autocomplete-shared/src/noop.ts diff --git a/ship.config.js b/ship.config.js index beaa5e8ed..9a21d6727 100644 --- a/ship.config.js +++ b/ship.config.js @@ -12,6 +12,7 @@ module.exports = { 'packages/autocomplete-plugin-algolia-insights', 'packages/autocomplete-plugin-query-suggestions', 'packages/autocomplete-plugin-recent-searches', + 'packages/autocomplete-plugin-tags', 'packages/autocomplete-preset-algolia', 'packages/autocomplete-shared', 'packages/autocomplete-theme-classic',