From 3defadd9bd628a3ebefce6d637f3a7e437b028f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 13 Jan 2021 16:28:49 +0100 Subject: [PATCH 01/22] docs(website): bootstrap new doc structure --- packages/website/docs/api.md | 6 +- packages/website/docs/basic-options.md | 8 + packages/website/docs/context.md | 2 +- packages/website/docs/controlled-mode.md | 9 - .../docs/createAlgoliaInsightsPlugin.md | 7 + .../creating-multi-source-autocompletes.md | 4 + packages/website/docs/getting-started.md | 40 +++ packages/website/docs/help.md | 8 + packages/website/docs/installation.md | 42 --- packages/website/docs/introduction.md | 8 + packages/website/docs/keyboard-navigation.md | 2 +- packages/website/docs/layout.md | 9 - packages/website/docs/more-resources.md | 12 - packages/website/docs/partials/wip.md | 4 + packages/website/docs/plugins.md | 7 +- packages/website/docs/prop-getters.md | 2 +- packages/website/docs/sources.md | 2 +- packages/website/docs/state.md | 2 +- packages/website/docs/upgrading.md | 7 +- .../docs/using-algolia-insights-plugin.md | 8 + .../using-dynamic-sources-based-on-query.md | 8 + .../docs/using-query-suggestions-plugin.md | 8 + .../docs/using-recent-searches-plugin.md | 8 + packages/website/docusaurus.config.js | 13 +- packages/website/package.json | 4 +- packages/website/sidebars.js | 97 ++++-- yarn.lock | 281 ++++++++++-------- 27 files changed, 353 insertions(+), 255 deletions(-) create mode 100644 packages/website/docs/basic-options.md delete mode 100644 packages/website/docs/controlled-mode.md create mode 100644 packages/website/docs/createAlgoliaInsightsPlugin.md create mode 100644 packages/website/docs/creating-multi-source-autocompletes.md create mode 100644 packages/website/docs/help.md delete mode 100644 packages/website/docs/installation.md create mode 100644 packages/website/docs/introduction.md delete mode 100644 packages/website/docs/layout.md delete mode 100644 packages/website/docs/more-resources.md create mode 100644 packages/website/docs/partials/wip.md create mode 100644 packages/website/docs/using-algolia-insights-plugin.md create mode 100644 packages/website/docs/using-dynamic-sources-based-on-query.md create mode 100644 packages/website/docs/using-query-suggestions-plugin.md create mode 100644 packages/website/docs/using-recent-searches-plugin.md diff --git a/packages/website/docs/api.md b/packages/website/docs/api.md index 96cbe1fda..90985c0b1 100644 --- a/packages/website/docs/api.md +++ b/packages/website/docs/api.md @@ -1,4 +1,8 @@ --- id: api -title: API +title: Introduction --- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/basic-options.md b/packages/website/docs/basic-options.md new file mode 100644 index 000000000..643841242 --- /dev/null +++ b/packages/website/docs/basic-options.md @@ -0,0 +1,8 @@ +--- +id: basic-options +title: Basic configuration options +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/context.md b/packages/website/docs/context.md index a467b943f..fc7d7d48d 100644 --- a/packages/website/docs/context.md +++ b/packages/website/docs/context.md @@ -1,6 +1,6 @@ --- id: context -title: Context +title: Accessing data with Context --- The autocomplete context allows to store data in the state to use in different lifecycle hooks. diff --git a/packages/website/docs/controlled-mode.md b/packages/website/docs/controlled-mode.md deleted file mode 100644 index 9f415a597..000000000 --- a/packages/website/docs/controlled-mode.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: controlled-mode -title: Controlled Mode ---- - - -:::caution -This page is incomplete. -::: diff --git a/packages/website/docs/createAlgoliaInsightsPlugin.md b/packages/website/docs/createAlgoliaInsightsPlugin.md new file mode 100644 index 000000000..4f78321a3 --- /dev/null +++ b/packages/website/docs/createAlgoliaInsightsPlugin.md @@ -0,0 +1,7 @@ +--- +id: createAlgoliaInsightsPlugin +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/creating-multi-source-autocompletes.md b/packages/website/docs/creating-multi-source-autocompletes.md new file mode 100644 index 000000000..3b68a61da --- /dev/null +++ b/packages/website/docs/creating-multi-source-autocompletes.md @@ -0,0 +1,4 @@ +--- +id: creating-multi-source-autocompletes +title: Creating multi-source autocompletes +--- diff --git a/packages/website/docs/getting-started.md b/packages/website/docs/getting-started.md index 70e8ddffa..c20692d7b 100644 --- a/packages/website/docs/getting-started.md +++ b/packages/website/docs/getting-started.md @@ -17,3 +17,43 @@ Autocomplete is a JavaScript library for **building autocomplete search experien - Plugs easily to Algolia's realtime search engine ## What is Autocomplete + +## Installation + +Autocomplete is available on the [npm](https://www.npmjs.com/) registry. + +### JavaScript + +```bash +yarn add @algolia/autocomplete-js@alpha +# or +npm install @algolia/autocomplete-js@alpha +``` + +If you do not wish to use a package manager, you can use standalone endpoints: + +```html + + + + + +``` + +### Headless + +```bash +yarn add @algolia/autocomplete-core@alpha +# or +npm install @algolia/autocomplete-core@alpha +``` + +If you do not wish to use a package manager, you can use standalone endpoints: + +```html + + + + + +``` diff --git a/packages/website/docs/help.md b/packages/website/docs/help.md new file mode 100644 index 000000000..82ce7c3ae --- /dev/null +++ b/packages/website/docs/help.md @@ -0,0 +1,8 @@ +--- +id: help +title: Help and Discussion +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/installation.md b/packages/website/docs/installation.md deleted file mode 100644 index cc79408f6..000000000 --- a/packages/website/docs/installation.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: installation -title: Installation ---- - -Autocomplete is available on the [npm](https://www.npmjs.com/) registry. - -## JavaScript - -```bash -yarn add @algolia/autocomplete-js@alpha -# or -npm install @algolia/autocomplete-js@alpha -``` - -If you do not wish to use a package manager, you can use standalone endpoints: - -```html - - - - - -``` - -## Headless - -```bash -yarn add @algolia/autocomplete-core@alpha -# or -npm install @algolia/autocomplete-core@alpha -``` - -If you do not wish to use a package manager, you can use standalone endpoints: - -```html - - - - - -``` diff --git a/packages/website/docs/introduction.md b/packages/website/docs/introduction.md new file mode 100644 index 000000000..3671e9512 --- /dev/null +++ b/packages/website/docs/introduction.md @@ -0,0 +1,8 @@ +--- +id: introduction +title: What is Autocomplete? +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/keyboard-navigation.md b/packages/website/docs/keyboard-navigation.md index de1f74370..5f9147b43 100644 --- a/packages/website/docs/keyboard-navigation.md +++ b/packages/website/docs/keyboard-navigation.md @@ -1,6 +1,6 @@ --- id: keyboard-navigation -title: Keyboard Navigation +title: Integrating keyboard navigation --- The Navigator API is used to redirect users when a suggestion link is opened programmatically using keyboard navigation. diff --git a/packages/website/docs/layout.md b/packages/website/docs/layout.md deleted file mode 100644 index d5503e065..000000000 --- a/packages/website/docs/layout.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -id: layout -title: Layout ---- - - -:::caution -This page is incomplete. -::: diff --git a/packages/website/docs/more-resources.md b/packages/website/docs/more-resources.md deleted file mode 100644 index 988ace0fd..000000000 --- a/packages/website/docs/more-resources.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: more-resources -title: More Resources ---- - -This pages lists all the resources for building great autocomplete search experiences. - -## Showcase - -## Guides - -## Examples diff --git a/packages/website/docs/partials/wip.md b/packages/website/docs/partials/wip.md new file mode 100644 index 000000000..cfd81438d --- /dev/null +++ b/packages/website/docs/partials/wip.md @@ -0,0 +1,4 @@ + +:::caution In progress +This page is in progress. +::: diff --git a/packages/website/docs/plugins.md b/packages/website/docs/plugins.md index a0f917886..9b13bd795 100644 --- a/packages/website/docs/plugins.md +++ b/packages/website/docs/plugins.md @@ -3,7 +3,6 @@ id: plugins title: Plugins --- - -:::caution -This page is incomplete. -::: +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/prop-getters.md b/packages/website/docs/prop-getters.md index 32c518ab4..0920c8162 100644 --- a/packages/website/docs/prop-getters.md +++ b/packages/website/docs/prop-getters.md @@ -1,6 +1,6 @@ --- id: prop-getters -title: Prop Getters +title: Advanced customization with Prop Getters --- The prop getters are functions that returns attributes and event handlers to create accessible and interactive autocomplete experiences. diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index fd0ab0038..eaa9f356f 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -1,6 +1,6 @@ --- id: sources -title: Sources +title: Populating autocomplete with Sources --- Sources are data that describes the suggestions and their behavior. diff --git a/packages/website/docs/state.md b/packages/website/docs/state.md index d7d35877b..e6dee6aad 100644 --- a/packages/website/docs/state.md +++ b/packages/website/docs/state.md @@ -1,6 +1,6 @@ --- id: state -title: State +title: Controlling behavior with State --- The autocomplete state drives the behavior of the experience. diff --git a/packages/website/docs/upgrading.md b/packages/website/docs/upgrading.md index 72c1f511d..698ce42ea 100644 --- a/packages/website/docs/upgrading.md +++ b/packages/website/docs/upgrading.md @@ -3,7 +3,6 @@ id: upgrading title: Upgrading --- - -:::caution -This page is incomplete. -::: +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/using-algolia-insights-plugin.md b/packages/website/docs/using-algolia-insights-plugin.md new file mode 100644 index 000000000..8a904f79f --- /dev/null +++ b/packages/website/docs/using-algolia-insights-plugin.md @@ -0,0 +1,8 @@ +--- +id: using-algolia-insights-plugin +title: Using the Algolia Insights plugin +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/using-dynamic-sources-based-on-query.md b/packages/website/docs/using-dynamic-sources-based-on-query.md new file mode 100644 index 000000000..e586dbe53 --- /dev/null +++ b/packages/website/docs/using-dynamic-sources-based-on-query.md @@ -0,0 +1,8 @@ +--- +id: using-dynamic-sources-based-on-query +title: Using dynamic sources based on the query +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/using-query-suggestions-plugin.md b/packages/website/docs/using-query-suggestions-plugin.md new file mode 100644 index 000000000..552107d6c --- /dev/null +++ b/packages/website/docs/using-query-suggestions-plugin.md @@ -0,0 +1,8 @@ +--- +id: using-query-suggestions-plugin +title: Using the Query Suggestions plugin +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docs/using-recent-searches-plugin.md b/packages/website/docs/using-recent-searches-plugin.md new file mode 100644 index 000000000..143c242e0 --- /dev/null +++ b/packages/website/docs/using-recent-searches-plugin.md @@ -0,0 +1,8 @@ +--- +id: using-recent-searches-plugin +title: Using the Recent Searches plugin +--- + +import Wip from './partials/wip.md' + + diff --git a/packages/website/docusaurus.config.js b/packages/website/docusaurus.config.js index 3c5d63f6d..8f3c92d12 100644 --- a/packages/website/docusaurus.config.js +++ b/packages/website/docusaurus.config.js @@ -16,14 +16,19 @@ module.exports = { }, items: [ { - to: 'docs/getting-started', label: 'Docs', + to: 'docs/introduction', position: 'right', }, { label: 'API', - type: 'doc', - docId: 'createAutocomplete', + to: 'docs/api', + position: 'right', + }, + { + label: 'Playground', + to: + 'https://codesandbox.io/s/github/algolia/autocomplete.js/tree/next/examples/js?file=/app.ts', position: 'right', }, { @@ -46,7 +51,7 @@ module.exports = { }, { label: 'API', - to: 'docs/createAutocomplete', + to: 'docs/api', }, ], }, diff --git a/packages/website/package.json b/packages/website/package.json index 2f3e848a5..c424b2fa1 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@docsearch/react": "1.0.0-alpha.28", - "@docusaurus/core": "2.0.0-alpha.69", - "@docusaurus/preset-classic": "2.0.0-alpha.69", + "@docusaurus/core": "2.0.0-alpha.70", + "@docusaurus/preset-classic": "2.0.0-alpha.70", "classnames": "2.2.6", "react": "17.0.1", "react-dom": "17.0.1" diff --git a/packages/website/sidebars.js b/packages/website/sidebars.js index 055e620e7..ed22167ed 100644 --- a/packages/website/sidebars.js +++ b/packages/website/sidebars.js @@ -2,45 +2,76 @@ module.exports = { docs: { - 'The Basics': [ - 'getting-started', - 'installation', + Introduction: ['introduction', 'getting-started', 'help'], + 'Core Concepts': [ + 'basic-options', + 'sources', 'state', 'context', - 'sources', 'keyboard-navigation', - 'layout', - 'prop-getters', - 'controlled-mode', + // 'prop-getters', 'plugins', - 'more-resources', - ], - Workflow: ['debugging', 'upgrading'], - Guides: ['creating-a-renderer'], - }, - api: { - 'autocomplete-core': ['createAutocomplete'], - 'autocomplete-js': [ - 'autocomplete-js', - 'getAlgoliaHits-js', - 'getAlgoliaResults-js', - 'highlightHit', - 'reverseHighlightHit', - 'snippetHit', - 'reverseSnippetHit', ], - 'autocomplete-preset-algolia': [ - 'getAlgoliaHits', - 'getAlgoliaResults', - 'parseAlgoliaHitHighlight', - 'parseAlgoliaHitReverseHighlight', - 'parseAlgoliaHitSnippet', - 'parseAlgoliaHitReverseSnippet', + Guides: [ + 'using-query-suggestions-plugin', + 'using-recent-searches-plugin', + 'using-algolia-insights-plugin', + 'creating-multi-source-autocompletes', + 'using-dynamic-sources-based-on-query', + 'creating-a-renderer', + 'upgrading', + 'debugging', ], - 'autocomplete-plugin-recent-searches': [ - 'createLocalStorageRecentSearchesPlugin', - 'createRecentSearchesPlugin', + 'API Reference': [ + 'api', + { + type: 'category', + label: 'autocomplete-core', + items: ['createAutocomplete'], + }, + { + type: 'category', + label: 'autocomplete-js', + items: [ + 'autocomplete-js', + 'getAlgoliaHits-js', + 'getAlgoliaResults-js', + 'highlightHit', + 'reverseHighlightHit', + 'snippetHit', + 'reverseSnippetHit', + ], + }, + { + type: 'category', + label: 'autocomplete-preset-algolia', + items: [ + 'getAlgoliaHits', + 'getAlgoliaResults', + 'parseAlgoliaHitHighlight', + 'parseAlgoliaHitReverseHighlight', + 'parseAlgoliaHitSnippet', + 'parseAlgoliaHitReverseSnippet', + ], + }, + { + type: 'category', + label: 'autocomplete-plugin-recent-searches', + items: [ + 'createLocalStorageRecentSearchesPlugin', + 'createRecentSearchesPlugin', + ], + }, + { + type: 'category', + label: 'autocomplete-plugin-query-suggestions', + items: ['createQuerySuggestionsPlugin'], + }, + { + type: 'category', + label: 'autocomplete-plugin-algolia-insights', + items: ['createAlgoliaInsightsPlugin'], + }, ], - 'autocomplete-plugin-query-suggestions': ['createQuerySuggestionsPlugin'], }, }; diff --git a/yarn.lock b/yarn.lock index 32b0222e4..30aaaa473 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1514,12 +1514,17 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@docsearch/css@3.0.0-alpha.32": + version "3.0.0-alpha.32" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.32.tgz#3d89c8db4035531d201f74ef2115f72094a24036" + integrity sha512-wafLX/jT1NPAwifPhzMJX394PjKdqf5TA4cz/JgvBYR1/+MiErLk/pyCmocXkawWGR17/6u2qw3wYvXu/Qe/DQ== + "@docsearch/css@^1.0.0-alpha.28": version "1.0.0-alpha.28" resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-1.0.0-alpha.28.tgz#c8a2cd8c1bb3a6855c51892e9dbdab5d42fe6e23" integrity sha512-1AhRzVdAkrWwhaxTX6/R7SnFHz8yLz1W8I/AldlTrfbNvZs9INk1FZiEFTJdgHaP68nhgQNWSGlQiDiI3y2RYg== -"@docsearch/react@1.0.0-alpha.28", "@docsearch/react@^1.0.0-alpha.27": +"@docsearch/react@1.0.0-alpha.28": version "1.0.0-alpha.28" resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-1.0.0-alpha.28.tgz#4f039ed79f8b3332b19a57677b219aebc5010e9d" integrity sha512-XjJOnCBXn+UZmtuDmgzlVIHnnvh6yHVwG4aFq8AXN6xJEIX3f180FvGaowFWAxgdtHplJxFGux0Xx4piHqBzIw== @@ -1529,12 +1534,23 @@ "@francoischalifour/autocomplete-preset-algolia" "^1.0.0-alpha.28" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-alpha.69.tgz#1452b9b2f2c5f67f75a45275e7eb5943c1f43a24" - integrity sha512-dJGbZ91QH9I5Nrhm0W6ZHe4j8Qv0RBclQkx3WayExxFSHgQUlBM0hBReJxAbTKc1uSJgG7OPpEWnzbZjyK9t/Q== +"@docsearch/react@^3.0.0-alpha.31": + version "3.0.0-alpha.32" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.32.tgz#ae3fa82e9c88683d9415bc439c4af7e2c0cfa5b7" + integrity sha512-2jqzPJu4y0mWiwwm+Kfgf/97Q8XaGxj1+jJfGJpJLkJyD8S2tK4OikyIRWI9gI9k3m48HxFm0+P8uAYYtIyjqA== + dependencies: + "@algolia/autocomplete-core" "^1.0.0-alpha.35" + "@algolia/autocomplete-preset-algolia" "^1.0.0-alpha.35" + "@docsearch/css" "3.0.0-alpha.32" + algoliasearch "^4.0.0" + +"@docusaurus/core@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-alpha.70.tgz#81bff8b093421a0c740fac02903dd23938806077" + integrity sha512-ccDcr5eb5T3C6k7VoqTclBFwjVkIHK1zISdhqzRNVl8AZTql1bYMvGUJP+2WbF6RSdmsGTNWreaUlrJc00dQqw== dependencies: "@babel/core" "^7.12.3" + "@babel/generator" "^7.12.5" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" "@babel/plugin-proposal-optional-chaining" "^7.12.1" "@babel/plugin-syntax-dynamic-import" "^7.8.3" @@ -1544,10 +1560,11 @@ "@babel/preset-typescript" "^7.12.1" "@babel/runtime" "^7.12.5" "@babel/runtime-corejs3" "^7.12.5" - "@docusaurus/cssnano-preset" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" - "@docusaurus/utils-validation" "2.0.0-alpha.69" + "@babel/traverse" "^7.12.5" + "@docusaurus/cssnano-preset" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" + "@docusaurus/utils-validation" "2.0.0-alpha.70" "@endiliey/static-site-generator-webpack-plugin" "^4.0.0" "@svgr/webpack" "^5.4.0" babel-loader "^8.2.1" @@ -1610,25 +1627,25 @@ webpack-merge "^4.2.2" webpackbar "^4.0.0" -"@docusaurus/cssnano-preset@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-alpha.69.tgz#19bc826850c9107c7bae507589284ac6f66b8485" - integrity sha512-Gv75LL4e2XnApNMPQ1mYVotH+0RKsg3WewPh7zSfEJvyzD6F+SHxIcu+tNSwkRMexlCLy6BHgOyEwvhom+VoaA== +"@docusaurus/cssnano-preset@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-alpha.70.tgz#75dc56a71dc365a450729fd109b86fab72a6f560" + integrity sha512-Zwk3SrlE5r/z5j/tjDcs4XoyeoyymCtEovoxLWLV7wb+iR1qb+Jdso4TRShAepbW/ff6SzjCZ8hRy8ahXPD9TA== dependencies: cssnano-preset-advanced "^4.0.7" postcss "^7.0.2" postcss-combine-duplicated-selectors "^9.1.0" postcss-sort-media-queries "^1.7.26" -"@docusaurus/mdx-loader@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.69.tgz#cf882670d2cb05902197776bba471d04696ceae9" - integrity sha512-yieXX7RhzLasN1bBj/tMj43l+DRu3VEyFRC63khYwfAZyhKtlMEL9eEaKMN3eqvnZD2u6G+nxXafwpqLdqNAWg== +"@docusaurus/mdx-loader@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.70.tgz#4cc3c92a5a89ffdc8313da998d4646564940b3e7" + integrity sha512-CDR4O4z7wO5/S8O3TAggCynnuBIGIlUT9q9uhhkDe8h5XDhF8n8d6bwqir0O+fUMN3EnyrMq6z1g4IDRB5G2vw== dependencies: "@babel/parser" "^7.12.5" "@babel/traverse" "^7.12.5" - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" "@mdx-js/mdx" "^1.6.21" "@mdx-js/react" "^1.6.21" escape-html "^1.0.3" @@ -1644,16 +1661,16 @@ url-loader "^4.1.1" webpack "^4.44.1" -"@docusaurus/plugin-content-blog@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.69.tgz#ba96b65c1d19452487330c860677ee20a5af5275" - integrity sha512-zN3c8ZiYpOid5f6YbxGZhVN8E5a4HmkyVJWTXts5tE0pdeiTuk0cfB5Iko4rnIDTztz7pJJCToIzKu07DcImww== +"@docusaurus/plugin-content-blog@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.70.tgz#795a5ddf181dfb314873a5dc33010d1a5bd94d28" + integrity sha512-qWXlSDovkhCZLJR0Wz4e3YcNjlelpuSNkS1rJ8sI1ehs/n32lj7A/nVoRfS/LnOMfIciY48vVPr64VLb6dfEeg== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/mdx-loader" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" - "@docusaurus/utils-validation" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/mdx-loader" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" + "@docusaurus/utils-validation" "2.0.0-alpha.70" chalk "^3.0.0" feed "^4.2.1" fs-extra "^9.0.1" @@ -1665,16 +1682,16 @@ remark-admonitions "^1.2.1" webpack "^4.44.1" -"@docusaurus/plugin-content-docs@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.69.tgz#5ef4d293765d02e11ffb2a201c491a34f92e2a83" - integrity sha512-u+2juUJWFd/v/x8NU4UzQf6k5tc21oZj6s/IFQDTse/5QeHkMrkw0aqUeHbGWet4foBRnx+021vmrPPqxeV/dQ== +"@docusaurus/plugin-content-docs@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.70.tgz#42dfa40786e819b42974dd167048b190b37bbee5" + integrity sha512-LZre12Q0sxLgi2XgjQbNQMV+jFG7v0+8hRzgBL+iCRiLCa4NlV7+M6mEHJGJJXSKqbfH7CelaUOESqEgPpVQXQ== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/mdx-loader" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" - "@docusaurus/utils-validation" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/mdx-loader" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" + "@docusaurus/utils-validation" "2.0.0-alpha.70" chalk "^3.0.0" execa "^3.4.0" fs-extra "^9.0.1" @@ -1693,87 +1710,89 @@ utility-types "^3.10.0" webpack "^4.44.1" -"@docusaurus/plugin-content-pages@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.69.tgz#215c52c7788b3794dc692ceb3d0a563adddc0435" - integrity sha512-RmEuhKFGagLyH42ggTweWHjaf82qbD9Xp7rXx10iHAUW2bJsMyGioag6gAw0Q+DeMMeHtMJdPsU99cuYqUdTpw== +"@docusaurus/plugin-content-pages@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.70.tgz#6cb937c9601d92bb616c7d95974d780d1a708ef7" + integrity sha512-HiFa5l1RDs155ATyYKkPtyIs/d6WJgSAyVfY5ji0Bsixp/K/Kh9YUZYMeTfeMIdhGYe3AAJz+PSZHYRpwTo1wA== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/mdx-loader" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" - "@docusaurus/utils-validation" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/mdx-loader" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" + "@docusaurus/utils-validation" "2.0.0-alpha.70" globby "^10.0.1" joi "^17.2.1" loader-utils "^1.2.3" + lodash "^4.17.19" minimatch "^3.0.4" remark-admonitions "^1.2.1" slash "^3.0.0" webpack "^4.44.1" -"@docusaurus/plugin-debug@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.69.tgz#643c3119d688ffa318eeb7d719942e1e24ad5a95" - integrity sha512-wvLBvntbuh25RCkKEeuwLl2h9OzMXZLIQWNa6FvAtmc8Xaomn3JkYnWn/fAKiBiOExN1ZEf9XomKVLNIUJ9xOg== +"@docusaurus/plugin-debug@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.70.tgz#7a41d24151a92195311c85ab827656cf705a0c68" + integrity sha512-h/x5KtS/YJerhY6C6sJOaP9gMaSVnjj1qZ6r9E/IFujQJ7bSKnk1unqBQpVXADkQhP081ENPL01ubc0/JbE1Mw== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" react-json-view "^1.19.1" -"@docusaurus/plugin-google-analytics@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.69.tgz#031e0e4431842b1c9ba99fb6dd6dd258ee91f05e" - integrity sha512-CrGy8DeJqMZdNmbLNyfgLtjMcapOoqY8jbJiKYCELI8SZ8Ns28i8Yh5W9qe4KxGqSQXvVWHg36xJVG/xAF/6jA== +"@docusaurus/plugin-google-analytics@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.70.tgz#9476314353d585716cbdd408319ff30bdbda4f87" + integrity sha512-Ah9W83ZnA0VvmflKNuGq5f/CaEjWJxhjkISQn09/ykEvXfWV33000Bhck4RoCr5YxD+GBEBT5suG5LKH7Qkigw== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" -"@docusaurus/plugin-google-gtag@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.69.tgz#b6d9c77e99f60dbffe2622f0567fce589bc420b3" - integrity sha512-mDAKBRkB3UJMuAUPqC43mNr8PPu3QuUbCLisJIvg7VMp9XoAcOImNZWmY2Smd+Law2tJkW3cBFPvm3fcVvJ1vA== +"@docusaurus/plugin-google-gtag@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.70.tgz#a90e54830a6f95a83cf51b82e7e6adcf6a699dc1" + integrity sha512-K3s894PqMPQnGXEZs0bSs2bRE3bVXFYSb/RN+K9sNd7zxGuOX4UytuvpXP+1r0Hj/YTwQIjj7AKsND0ZpDJHyw== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" -"@docusaurus/plugin-sitemap@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.69.tgz#ffda02b4be468b3dfeed8fde3c2f2971ee839078" - integrity sha512-DqrXm1PiWWNN5wbY1iVg0sdkCL3KZEj7uyjmZfXIfMQaEF2ErqJDY07QAwANrl27huPQmmc9tYwW3FqgKbVlbA== +"@docusaurus/plugin-sitemap@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.70.tgz#1eb02e4a4ecf5fb2bdf641a6f962ae421ff86916" + integrity sha512-ev9yNLPoeHP03jTz67daGd7yA7YhUwHeoWz14SyiKuU7OYtwL/8SJTn/V5kMDRl7o8FRQt9T//mRkpa270hmXw== dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" fs-extra "^9.0.1" joi "^17.2.1" sitemap "^3.2.2" -"@docusaurus/preset-classic@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.69.tgz#f8ecfd610852e6754037f006edc38dc4d6146da6" - integrity sha512-4RiLXB+1vYzgTxGRnZqrYU/7gSsqUWYbofy7Ce5i4NF+L65B0mnBGaBhU0j1ZQJUMuozByRbf54wExZB25Re0A== - dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/plugin-content-blog" "2.0.0-alpha.69" - "@docusaurus/plugin-content-docs" "2.0.0-alpha.69" - "@docusaurus/plugin-content-pages" "2.0.0-alpha.69" - "@docusaurus/plugin-debug" "2.0.0-alpha.69" - "@docusaurus/plugin-google-analytics" "2.0.0-alpha.69" - "@docusaurus/plugin-google-gtag" "2.0.0-alpha.69" - "@docusaurus/plugin-sitemap" "2.0.0-alpha.69" - "@docusaurus/theme-classic" "2.0.0-alpha.69" - "@docusaurus/theme-search-algolia" "2.0.0-alpha.69" - -"@docusaurus/theme-classic@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.69.tgz#64885b973df695e52356e3484fd9a03101b86e58" - integrity sha512-BterTyQvGrHS4oC/USXDG6GHt90oWUFyDg7oQ8WAa2zfkZTgcM3LiZeUYbn8vwy/HVU7hOWCixPTtY3R9iNZ9Q== - dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/plugin-content-blog" "2.0.0-alpha.69" - "@docusaurus/plugin-content-docs" "2.0.0-alpha.69" - "@docusaurus/plugin-content-pages" "2.0.0-alpha.69" - "@docusaurus/theme-common" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - "@docusaurus/utils-validation" "2.0.0-alpha.69" +"@docusaurus/preset-classic@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.70.tgz#7857f606eecdbaa34f8df83d62812908be02126f" + integrity sha512-Zx98KryJjHiqzGisWKR0glXl0HXuf/YbcK9yUl6ySyS+6cIMAuGMS0HGLgbvvEmYjywz7nMLpijzGderEOihjQ== + dependencies: + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/plugin-content-blog" "2.0.0-alpha.70" + "@docusaurus/plugin-content-docs" "2.0.0-alpha.70" + "@docusaurus/plugin-content-pages" "2.0.0-alpha.70" + "@docusaurus/plugin-debug" "2.0.0-alpha.70" + "@docusaurus/plugin-google-analytics" "2.0.0-alpha.70" + "@docusaurus/plugin-google-gtag" "2.0.0-alpha.70" + "@docusaurus/plugin-sitemap" "2.0.0-alpha.70" + "@docusaurus/theme-classic" "2.0.0-alpha.70" + "@docusaurus/theme-search-algolia" "2.0.0-alpha.70" + +"@docusaurus/theme-classic@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.70.tgz#58e2dceee1076980700865df460e771e8d78cb68" + integrity sha512-lKU+fgSd08fo3LNYTw31Wty7RgAdFm8bEOwBNkKZcCFnatTSG4qyDbrDZclCQT/SpXSv9XIEKUc0irg2IH6Qrg== + dependencies: + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/plugin-content-blog" "2.0.0-alpha.70" + "@docusaurus/plugin-content-docs" "2.0.0-alpha.70" + "@docusaurus/plugin-content-pages" "2.0.0-alpha.70" + "@docusaurus/theme-common" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" + "@docusaurus/utils-validation" "2.0.0-alpha.70" "@mdx-js/mdx" "^1.6.21" "@mdx-js/react" "^1.6.21" "@types/react-toggle" "^4.0.2" @@ -1789,25 +1808,26 @@ react-router-dom "^5.2.0" react-toggle "^4.1.1" -"@docusaurus/theme-common@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-alpha.69.tgz#4fd13db9fdd2b94e2010cd579ccfacde41824839" - integrity sha512-6Pq+gmmYOey86z0HaOOyIWMg+ftcl8KR0bamNQD3wkBtiEy7ymcHMlck887W+fFxttWLPo4+/HzfYFUqSoCHkQ== - dependencies: - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/plugin-content-blog" "2.0.0-alpha.69" - "@docusaurus/plugin-content-docs" "2.0.0-alpha.69" - "@docusaurus/plugin-content-pages" "2.0.0-alpha.69" - "@docusaurus/types" "2.0.0-alpha.69" - -"@docusaurus/theme-search-algolia@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.69.tgz#955da97fd3052118fee1b5ffa1abfa33043fd67a" - integrity sha512-hPg54EwZudgqjs7D6QbWMbG3XmI10XtcqED9VMg6R3LPBG3317UhtuEGa1jJrF6hDN/HHbzWZOQ00LLBHpf9+Q== - dependencies: - "@docsearch/react" "^1.0.0-alpha.27" - "@docusaurus/core" "2.0.0-alpha.69" - "@docusaurus/utils" "2.0.0-alpha.69" +"@docusaurus/theme-common@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-alpha.70.tgz#fa42aef2ec1b535d37f72fc978a3138c49667a37" + integrity sha512-Ge/dLGPCJhtyvumSMg0BlWcF00d1Qd2KnHf8kL/0nTxe257yNTHIOK95LKhIPAdcVgxG+ge9N0XcBm4KaubASQ== + dependencies: + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/plugin-content-blog" "2.0.0-alpha.70" + "@docusaurus/plugin-content-docs" "2.0.0-alpha.70" + "@docusaurus/plugin-content-pages" "2.0.0-alpha.70" + "@docusaurus/types" "2.0.0-alpha.70" + +"@docusaurus/theme-search-algolia@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.70.tgz#7f41241e0d22e89438817a3d4a27d880116c06c1" + integrity sha512-xuoWZ+HUKzn1A5vPlNZM8mtyRL5uo15o34OX/i7HkTRmBVymWO1bBE0lECfDVJU2JUYGmwjpDXhZzNLDZmZRWg== + dependencies: + "@docsearch/react" "^3.0.0-alpha.31" + "@docusaurus/core" "2.0.0-alpha.70" + "@docusaurus/theme-common" "2.0.0-alpha.70" + "@docusaurus/utils" "2.0.0-alpha.70" algoliasearch "^4.0.0" algoliasearch-helper "^3.1.1" clsx "^1.1.1" @@ -1815,35 +1835,36 @@ joi "^17.2.1" lodash "^4.17.19" -"@docusaurus/types@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-alpha.69.tgz#05d1a28f325600185b3cb4088326c865fb4b54a9" - integrity sha512-8TgHmUMH5q+5D93nyugk/dtUeGPblRE++gxxrwjNYnJucRUNDKRC8kJhEozODGcSfXddTeMalPvbRKSz9Pxj2g== +"@docusaurus/types@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-alpha.70.tgz#44b98290919cca2505aea334daecf762c7537d10" + integrity sha512-QoHmMiJhRDq5P/4o3eUIiJebdwRjShFlal01DST5B8MZo4k0ogl57FNHqJvIHc93NgonZzFlvC/auLlBnc/d4Q== dependencies: "@types/webpack" "^4.41.0" commander "^4.0.1" querystring "0.2.0" webpack-merge "^4.2.2" -"@docusaurus/utils-validation@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-alpha.69.tgz#35adab0f2b787dfdcf508ea0aa3552578be583a5" - integrity sha512-kjSZS8WOCVlqCLHOhbIDqztmSAPkBva51oT/oohs4WaNdvT6e0PdLycUA2Dg3pXLw1FXsiMxluCYyC8BGX0B+Q== +"@docusaurus/utils-validation@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-alpha.70.tgz#04f24a7b3a4568ca164a8c1a4cf0caa8ba5caa6e" + integrity sha512-GJonaRjiJtlCk1+RfKA9f0YwRsSRGFMVbl6DrFidTgs4FmRb0hQsN4fnllsBvBJtbDZYwPTQ3T7c4cKJ/Ll7bQ== dependencies: - "@docusaurus/utils" "2.0.0-alpha.69" + "@docusaurus/utils" "2.0.0-alpha.70" chalk "^3.0.0" joi "^17.2.1" -"@docusaurus/utils@2.0.0-alpha.69": - version "2.0.0-alpha.69" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-alpha.69.tgz#9532cd489742beede570575ab70265dacecb3f24" - integrity sha512-RpxqcjPT0L+MxLyS/4QOHp/2hlKPcPoDyvfqtTJiS9DPtUzkH573a5/yMbfzz8IbPeYWRCPL2qxWtmN7XCZ/sQ== +"@docusaurus/utils@2.0.0-alpha.70": + version "2.0.0-alpha.70" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-alpha.70.tgz#01779efcb4ff3bf39f9e74b3ef06fc2c8a43633a" + integrity sha512-xNSUcE7fGcneH00CPCEY0SP5V7H6pLEcu620UiU/m1367tCMsmv+MZcnII2ACcjAtvhjS22v/KLippM3VeTXqQ== dependencies: - "@docusaurus/types" "2.0.0-alpha.69" + "@docusaurus/types" "2.0.0-alpha.70" chalk "^3.0.0" escape-string-regexp "^2.0.0" fs-extra "^9.0.1" gray-matter "^4.0.2" + lodash "^4.17.20" lodash.camelcase "^4.3.0" lodash.kebabcase "^4.1.1" resolve-pathname "^3.0.0" From b060dc50d8bdff05e45fd82170c9755b1257d655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 13 Jan 2021 16:37:00 +0100 Subject: [PATCH 02/22] docs(website): add doc for `createAlgoliaInsightsPlugin` --- .../src/createAlgoliaInsightsPlugin.ts | 4 +- .../src/types/EventParams.ts | 2 +- .../docs/createAlgoliaInsightsPlugin.md | 83 ++++++++++++++++++- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts index e2c606f46..cedbe3702 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/createAlgoliaInsightsPlugin.ts @@ -12,7 +12,7 @@ import { AlgoliaInsightsHit, InsightsApi, InsightsClient, - OnHighlightParams, + OnActiveParams, OnItemsChangeParams, OnSelectParams, } from './types'; @@ -59,7 +59,7 @@ export type CreateAlgoliaInsightsPluginParams = { /** * Hook to send an Insights event when an item is active. */ - onActive?(params: OnHighlightParams): void; + onActive?(params: OnActiveParams): void; }; export function createAlgoliaInsightsPlugin({ diff --git a/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts b/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts index b66d1d406..d0ca19a05 100644 --- a/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts +++ b/packages/autocomplete-plugin-algolia-insights/src/types/EventParams.ts @@ -15,7 +15,7 @@ export type OnSelectParams = { event: any; }; -export type OnHighlightParams = OnSelectParams; +export type OnActiveParams = OnSelectParams; export type OnItemsChangeParams = { insights: InsightsApi; diff --git a/packages/website/docs/createAlgoliaInsightsPlugin.md b/packages/website/docs/createAlgoliaInsightsPlugin.md index 4f78321a3..1268bb618 100644 --- a/packages/website/docs/createAlgoliaInsightsPlugin.md +++ b/packages/website/docs/createAlgoliaInsightsPlugin.md @@ -2,6 +2,85 @@ id: createAlgoliaInsightsPlugin --- -import Wip from './partials/wip.md' +## Example - +```ts +import algoliasearch from 'algoliasearch/lite'; +import { autocomplete } from '@algolia/autocomplete-js'; +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; +import insightsClient from 'search-insights'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + +autocomplete({ + container: '#autocomplete', + plugins: [algoliaInsightsPlugin], +}); +``` + +## Import + +```ts +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; +``` + +## Params + +### `insightsClient` + +> `InsightsClient` | required + +The initialized Search Insights client. + +### `onItemsChange` + +> `(params: OnItemsChangeParams) => void` + +Hook to send an Insights event when the items change. + +This hook is debounced every 400ms to better reflect when items are acknowledged by the user. + +```ts +type OnItemsChangeParams = { + insights: InsightsApi; + insightsEvents: ViewedObjectIDsParams[]; + state: AutocompleteState; +}; +``` + +### `onSelect` + +> `(params: OnSelectParams) => void` + +Hook to send an Insights event when an item is selected. + +```ts +type OnSelectParams = { + insights: InsightsApi; + insightsEvents: ClickedObjectIDsAfterSearchParams[]; + item: AlgoliaInsightsHit; + state: AutocompleteState; + event: any; +}; +``` + +### `onActive` + +> `(params: OnActiveParams) => void` + +Hook to send an Insights event when an item is active. + +```ts +type OnActiveParams = { + insights: InsightsApi; + insightsEvents: ClickedObjectIDsAfterSearchParams[]; + item: AlgoliaInsightsHit; + state: AutocompleteState; + event: any; +}; +``` From 8a3a908920eef9ae220160474af0df1f5a07073f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 14 Jan 2021 14:43:00 +0100 Subject: [PATCH 03/22] docs(website): add draft sections --- packages/website/docs/api.md | 4 +-- packages/website/docs/basic-options.md | 16 +++++++-- packages/website/docs/context.md | 12 +++++++ packages/website/docs/getting-started.md | 18 ++++++++++ packages/website/docs/help.md | 10 ++++-- packages/website/docs/introduction.md | 4 +-- packages/website/docs/partials/draft.md | 5 +++ packages/website/docs/partials/wip.md | 4 --- packages/website/docs/plugins.md | 19 +++++++++-- packages/website/docs/sources.md | 33 +++++++++++++++++++ packages/website/docs/state.md | 30 +++++++++++++++++ packages/website/docs/upgrading.md | 4 +-- .../docs/using-algolia-insights-plugin.md | 4 +-- .../using-dynamic-sources-based-on-query.md | 4 +-- .../docs/using-query-suggestions-plugin.md | 4 +-- .../docs/using-recent-searches-plugin.md | 4 +-- 16 files changed, 151 insertions(+), 24 deletions(-) create mode 100644 packages/website/docs/partials/draft.md delete mode 100644 packages/website/docs/partials/wip.md diff --git a/packages/website/docs/api.md b/packages/website/docs/api.md index 90985c0b1..9f4b148d0 100644 --- a/packages/website/docs/api.md +++ b/packages/website/docs/api.md @@ -3,6 +3,6 @@ id: api title: Introduction --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/basic-options.md b/packages/website/docs/basic-options.md index 643841242..95c7f513a 100644 --- a/packages/website/docs/basic-options.md +++ b/packages/website/docs/basic-options.md @@ -3,6 +3,18 @@ id: basic-options title: Basic configuration options --- -import Wip from './partials/wip.md' +:::note Draft - +This page needs to cover: + +- There are only two required params to create an autocomplete: + - You need to provide a selector for the **container** you want autocomplete to appear in. + - You need to define where to get the items to display using **getSources** (or a **plug-in** which provides **getSources**). Check our **Sources** core concept for more information. +- Beyond this, there are many parameters you can use to customize the experience and help you with development. Here are some commonly used ones: + - Use **placeholder** to define the text that appears in the input when a user hasn’t typed anything. + - Use **autoFocus** to focus on the search box when the page is loaded and **openOnFocus** to display items as soon as a user selects the autocomplete (without typing anything). + - Use the **onStateChange** hook to call a function whenever the state changes (see our **State** core concept for more info). + - Use **`debug: true`** to keep the autocomplete panel with items open, even when the blur event occurs. (This is only meant to be used during development. See our **Debugging guide** for more info.) +- Check out the **API reference** for a full list of params. + +::: diff --git a/packages/website/docs/context.md b/packages/website/docs/context.md index fc7d7d48d..4cca3f710 100644 --- a/packages/website/docs/context.md +++ b/packages/website/docs/context.md @@ -5,6 +5,18 @@ title: Accessing data with Context The autocomplete context allows to store data in the state to use in different lifecycle hooks. +:::note Draft + +This page needs to cover: + +- You can use **context** to store and access data in the **state**. You can think of it as a global variable. +- For example, you can use **context** to store data regarding the number of hits from an Algolia response, and then use this when creating **templates** in your sources. Without storing this value in the **context**, you wouldn’t have access to it in the templates. +- Like all setters, **setContext** expects an object that it will merge with the previous context object. + - Code snippet (including setContext and using context.nbHits in a template) +- **Plugins** can also store their API in **context** + +::: + You can use this API to access data in the templates that you would otherwise have access only in `getSources` for instance. The `setContext` setters expects an object that will be merged with the previous context. You can then read the context in `state.context`. The following example stores the number of hits from an Algolia response to display it in the templates. diff --git a/packages/website/docs/getting-started.md b/packages/website/docs/getting-started.md index c20692d7b..3662df3d7 100644 --- a/packages/website/docs/getting-started.md +++ b/packages/website/docs/getting-started.md @@ -3,6 +3,24 @@ id: getting-started title: Getting Started --- +:::note Draft + +This page needs to cover: + +- These docs provide a few ways to learn how to use Autocomplete: + - Read about **Core Concepts**—here you can learn more about underlying principles, like **Sources** and **State**. + - Follow our **Guides** to understand how to build common UX patterns. + - Refer to **API reference**. + - [Maybe v2] Play in the **Playground** where you can select components of your autocomplete and we provide you with the code. +- Keep reading for a simple sample implementation +- Installation + - JS + - Core +- A basic example + - Commented code snippet on a basic example (only using query-suggestions plug-in) with resulting UI and links to various **Core Concepts.** + +::: + This page is an overview of the Autocomplete documentation and related resources. Autocomplete is a JavaScript library for **building autocomplete search experiences**. diff --git a/packages/website/docs/help.md b/packages/website/docs/help.md index 82ce7c3ae..0d04e6ab9 100644 --- a/packages/website/docs/help.md +++ b/packages/website/docs/help.md @@ -3,6 +3,12 @@ id: help title: Help and Discussion --- -import Wip from './partials/wip.md' + +:::note Draft +This page needs to cover: - +- Ask questions and discuss with other community members on [GitHub](https://github.com/algolia/autocomplete.js/discussions/new) +- Request a feature to add to Autocomplete on [GitHub](https://github.com/algolia/autocomplete.js/discussions/new) +- Report a bug on [GitHub](https://github.com/algolia/autocomplete.js/issues/new?template=Bug_report.md) + +::: diff --git a/packages/website/docs/introduction.md b/packages/website/docs/introduction.md index 3671e9512..df8f5609c 100644 --- a/packages/website/docs/introduction.md +++ b/packages/website/docs/introduction.md @@ -3,6 +3,6 @@ id: introduction title: What is Autocomplete? --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/partials/draft.md b/packages/website/docs/partials/draft.md new file mode 100644 index 000000000..c87903062 --- /dev/null +++ b/packages/website/docs/partials/draft.md @@ -0,0 +1,5 @@ +:::note Draft + +This page is a draft. + +::: diff --git a/packages/website/docs/partials/wip.md b/packages/website/docs/partials/wip.md deleted file mode 100644 index cfd81438d..000000000 --- a/packages/website/docs/partials/wip.md +++ /dev/null @@ -1,4 +0,0 @@ - -:::caution In progress -This page is in progress. -::: diff --git a/packages/website/docs/plugins.md b/packages/website/docs/plugins.md index 9b13bd795..3ffe9c8d7 100644 --- a/packages/website/docs/plugins.md +++ b/packages/website/docs/plugins.md @@ -3,6 +3,21 @@ id: plugins title: Plugins --- -import Wip from './partials/wip.md' +:::note Draft - +This page needs to cover: + +- Plugins are a way of encapsulating sources, including the data and display. They can also trigger particular actions on an interaction. +- For example, the insights plugin sends click and conversion events. +- We provide some out-of-the-box plugins, but you can also build your own. +- As a simple example, you could create a plugin to display a static list of predefined items: + - Code snippet +- We also have created a couple out-of-the box plugins: + - Our **recent-searches plug-in** stores recently made searches in local storage so that you can incorporate an individual’s recent searches into an autocomplete. + - Code snippet + live example + - Our **query-suggestions plug-in** is made to work with an Algolia Query Suggestions index. + - Code snippet + live example + +We'll also need to document the Plugin API. + +::: diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index eaa9f356f..be7fdb937 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -5,6 +5,39 @@ title: Populating autocomplete with Sources Sources are data that describes the suggestions and their behavior. +:::note Draft + +This page needs to cover: + +- **Sources** define the data sources where to retrieve the items to display in the autocomplete. + - For example, you could hardcode an array of static items to display. + - Code snippet + - You could also search for matches into a list of hard-coded items: + - Code snippet +- Before moving on to more complex sources, let’s take a closer look at these static sources. + - You’ll notice that **getSources** returns an array of sources. + - Each source uses the **getItems** function to return the actual items to display. The items could just be a static array, or you could use a function to filter/refine the items based on the query. The **getItems** function is called each time the input changes. + - [Optional] You can use **getItemInputValue** to fill the search box input with a particular attribute of an item. By default, the input will show the same query a user has typed in. + - Code snippet + - [Optional] You can use **getItemURL** to add [keyboard accessibility](https://autocomplete.algolia.com/docs/keyboard-navigation) features to let users open items in the current tab, in a new tab or in a new window. + - Code snippet + - In addition to defining the data sources for items in the autocomplete, a source also defines how to display the items using **templates.** + - A **template** can either return a string: + - Code snippet + - Or a **template** can be a Preact component: + - Code snippet + - Or a **template** can be HTML: + - Code snippet similar to [this sandbox](https://codesandbox.io/s/algoliajs-example-forked-298f6?file=/app.tsx) + - Or a **template** can be a component using your own `createElement` and `Fragment`: + - Code snippet +- Static sources can be useful, particularly if a user hasn’t typed anything yet. (See our guide on using **dynamic sources based on the query**.) But you may want a more complex search beyond just looking for exact matches in strings. In that case, you can search into one or more Algolia indices using the built-in **getAlgoliaHits** method**:** + - **Code snippet** + - **getAlgoliaHits expects a searchClient and one or more queries (link relevant Algolia docs)** +- **getSources** support promises, which means that you can fetch your sources from an asynchronous API: + - Code snippet + +::: + ## Examples ### Using static sources diff --git a/packages/website/docs/state.md b/packages/website/docs/state.md index e6dee6aad..ab946e486 100644 --- a/packages/website/docs/state.md +++ b/packages/website/docs/state.md @@ -5,6 +5,36 @@ title: Controlling behavior with State The autocomplete state drives the behavior of the experience. +:::note Draft + +This page needs to cover: + +- State is the underlying set of properties that drives the autocomplete behavior. For example, the **query** state is the value in the input to search and retrieve items for. As the query state changes, the items retrieved and displayed from the **sources** change. +- Autocomplete state is made up of: + - query - the value in the input to search for + - selectedItemId - which item (if any) is selected + - completion - the completed version of the input text + - isOpen - if the autocomplete display panel is open + - status - 'idle' | 'loading' | 'stalled' | 'error' + - collections - **Sources** and items powering the experience + - context - global state passed to lifecycle hooks, see more in **Context** +- You can set an **initialState** when instantiating an autocomplete. + - Code snippet +- State changes occur automatically when a user changes the input, selects an item, etc. You can also manually set the state using setters + - For example, you may want to manually set the query in some cases + - Code snippet + - This is the full list of setters: + - setQuery + - setSelectedItemId + - setIsOpen + - setStatus + - setCollections + - setContext +- Finally, you can listen for state changes using **onStateChange** + - Code snippet + +::: + The state is passed to all lifecycle hooks so that you can customize the behavior. ## Examples diff --git a/packages/website/docs/upgrading.md b/packages/website/docs/upgrading.md index 698ce42ea..d0744a049 100644 --- a/packages/website/docs/upgrading.md +++ b/packages/website/docs/upgrading.md @@ -3,6 +3,6 @@ id: upgrading title: Upgrading --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/using-algolia-insights-plugin.md b/packages/website/docs/using-algolia-insights-plugin.md index 8a904f79f..b2e309592 100644 --- a/packages/website/docs/using-algolia-insights-plugin.md +++ b/packages/website/docs/using-algolia-insights-plugin.md @@ -3,6 +3,6 @@ id: using-algolia-insights-plugin title: Using the Algolia Insights plugin --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/using-dynamic-sources-based-on-query.md b/packages/website/docs/using-dynamic-sources-based-on-query.md index e586dbe53..f7b81c14a 100644 --- a/packages/website/docs/using-dynamic-sources-based-on-query.md +++ b/packages/website/docs/using-dynamic-sources-based-on-query.md @@ -3,6 +3,6 @@ id: using-dynamic-sources-based-on-query title: Using dynamic sources based on the query --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/using-query-suggestions-plugin.md b/packages/website/docs/using-query-suggestions-plugin.md index 552107d6c..f380c8222 100644 --- a/packages/website/docs/using-query-suggestions-plugin.md +++ b/packages/website/docs/using-query-suggestions-plugin.md @@ -3,6 +3,6 @@ id: using-query-suggestions-plugin title: Using the Query Suggestions plugin --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + diff --git a/packages/website/docs/using-recent-searches-plugin.md b/packages/website/docs/using-recent-searches-plugin.md index 143c242e0..1419e93b5 100644 --- a/packages/website/docs/using-recent-searches-plugin.md +++ b/packages/website/docs/using-recent-searches-plugin.md @@ -3,6 +3,6 @@ id: using-recent-searches-plugin title: Using the Recent Searches plugin --- -import Wip from './partials/wip.md' +import Draft from './partials/draft.md' - + From 48f13563a304e3ce3ee0cf57eab4efa2b2b07e6b Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Thu, 14 Jan 2021 17:32:13 +0100 Subject: [PATCH 04/22] docs: rewrite "Integrating keyboard navigation" (#389) --- packages/website/docs/keyboard-navigation.md | 37 +++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/website/docs/keyboard-navigation.md b/packages/website/docs/keyboard-navigation.md index 5f9147b43..51818ec2b 100644 --- a/packages/website/docs/keyboard-navigation.md +++ b/packages/website/docs/keyboard-navigation.md @@ -3,20 +3,21 @@ id: keyboard-navigation title: Integrating keyboard navigation --- -The Navigator API is used to redirect users when a suggestion link is opened programmatically using keyboard navigation. +The Navigator API redirects users when opening a suggestion using their keyboard. -This API defines how a URL should be opened with different key modifiers: +**Keyboard navigation is essential to a satisfying autocomplete experience.** This is one of the most important aspects of web accessibility: users should be able to interact with an autocomplete without using a mouse or trackpad. -- **In the current tab** triggered on Enter -- **In a new tab** triggered on ⌘ Cmd+Enter or Ctrl+Enter. -- **In a new window** triggered on ⇧ Shift+Enter +Autocomplete provides keyboard accessibility out of the box and lets you define how to navigate to results without leaving the keyboard. - -:::important -To activate keyboard navigation, use [`getItemUrl`](createAutocomplete#getitemurl) in your source to provide the value to process as a URL. This indicates the navigator API which links to open on Enter. -::: +The Navigator API defines three navigation schemes based on key combinations: -## Example +- **In the current tab** when hitting Enter +- **In a new tab** when hitting ⌘ Cmd+Enter or Ctrl+Enter +- **In a new window** when hitting ⇧ Shift+Enter + +## Usage + +To activate keyboard navigation, you need to implement a [`getItemUrl`](createAutocomplete#getitemurl) function in each of your [sources](/docs/sources) to provide the URL to navigate to. It tells the Navigator API which link to open on Enter. ```js {6-8} const autocomplete = createAutocomplete({ @@ -33,7 +34,7 @@ const autocomplete = createAutocomplete({ }, ]; }, - // Default navigator values + // Default Navigator API implementation navigator: { navigate({ itemUrl }) { window.location.assign(itemUrl); @@ -52,7 +53,9 @@ const autocomplete = createAutocomplete({ }); ``` -If you use autocomplete in a [Gatsby](https://www.gatsbyjs.org/) website, you can leverage their [`navigate`](https://www.gatsbyjs.org/docs/gatsby-link/) API to avoid hard refreshes. +By default, the Navigator API uses the [`Location`](https://developer.mozilla.org/en-US/docs/Web/API/Location) API (see default implementation above). If you're relying on native document-based routing, this should work out of the box. If you're using custom client-side routing, you can use the Navigator API to connect your autocomplete with it. + +For example, if you're using Autocomplete in a [Gatsby](https://www.gatsbyjs.org/) website, you can leverage their [`navigate`](https://www.gatsbyjs.org/docs/gatsby-link/) helper to navigate to internal pages without refreshing the page. ```js import { navigate } from 'gatsby'; @@ -66,15 +69,15 @@ const autocomplete = createAutocomplete({ }); ``` -## Params +## Reference -The provided params get merged with the default configuration so that you don't have to rewrite all methods. +Autocomplete merges the provided parameters with the default configuration, so you can only rewrite what you need. ### `navigate` > `(params: { itemUrl: string, item: TItem, state: AutocompleteState }) => void` -Function called when a URL should be open in the current page. +The function called when a URL should open in the current page. This is triggered on Enter. @@ -82,7 +85,7 @@ This is triggered on Enter. > `(params: { itemUrl: string, item: TItem, state: AutocompleteState }) => void` -Function called when a URL should be open in a new tab. +The function called when a URL should open in a new tab. This is triggered on ⌘ Cmd+Enter or Ctrl+Enter. @@ -90,6 +93,6 @@ This is triggered on ⌘ Cmd+Enter or Ctrl+ `(params: { itemUrl: string, item: TItem, state: AutocompleteState }) => void` -Function called when a URL should be open in a new window. +The function called when a URL should open in a new window. This is triggered on ⇧ Shift+Enter. From 8c3c8845912d790a44c471094a1e3d0b87f6bc10 Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Mon, 18 Jan 2021 11:09:40 +0100 Subject: [PATCH 05/22] docs: rewrite "Accessing data with Context" (#390) --- packages/website/docs/context.md | 73 ++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/website/docs/context.md b/packages/website/docs/context.md index 4cca3f710..e75b63807 100644 --- a/packages/website/docs/context.md +++ b/packages/website/docs/context.md @@ -3,23 +3,17 @@ id: context title: Accessing data with Context --- -The autocomplete context allows to store data in the state to use in different lifecycle hooks. +The Context lets you store data and access it in different lifecycle hooks. -:::note Draft +Sometimes you need to store arbitrary data so you can access it later in your autocomplete. For example, when retrieving hits from Algolia, you may want to reuse the total number of retrieved hits in a template. -This page needs to cover: +Autocomplete lets you store data using its Context API and access it anywhere from the [state](/docs/state). -- You can use **context** to store and access data in the **state**. You can think of it as a global variable. -- For example, you can use **context** to store data regarding the number of hits from an Algolia response, and then use this when creating **templates** in your sources. Without storing this value in the **context**, you wouldn’t have access to it in the templates. -- Like all setters, **setContext** expects an object that it will merge with the previous context object. - - Code snippet (including setContext and using context.nbHits in a template) -- **Plugins** can also store their API in **context** +## Usage -::: +Context exposes a `setContext` function, which takes an object and merges it with the existing context. You can then access the context in `state.context`. -You can use this API to access data in the templates that you would otherwise have access only in `getSources` for instance. The `setContext` setters expects an object that will be merged with the previous context. You can then read the context in `state.context`. - -The following example stores the number of hits from an Algolia response to display it in the templates. +The following example stores the number of hits from an Algolia response, making it accessible everywhere in your autocomplete. ```js const autocomplete = createAutocomplete({ @@ -33,15 +27,13 @@ const autocomplete = createAutocomplete({ query, }, ], - }).then((results) => { - const productsResults = results[0]; - + }).then(([products]) => { setContext({ - nbProducts: productsResults.nbHits, + nbProducts: products.nbHits, }); - // You can now use `state.context.nbProducts` anywhere you have access to - // the state. + // You can now use `state.context.nbProducts` + // anywhere where you have access to `state`. return [ // ... @@ -50,3 +42,48 @@ const autocomplete = createAutocomplete({ }, }); ``` + +Context can be handy when developing [Autocomplete plugins](/docs/plugins). It avoids polluting the global namespace while still being able to pass data around across different lifecycle hooks. + +```js +function createAutocompletePlugin() { + return { + // ... + subscribe({ setContext }) { + setContext({ + autocompletePlugin: { + // ... + }, + }); + }, + }; +} +``` + +## Reference + +The `setContext` function is accessible on your `autocomplete` instance. + +It's also provided in: +- [`getSources`](createAutocomplete#getsources) +- [`onInput`](createAutocomplete#oninput) +- [`onSubmit`](createAutocomplete#onsubmit) +- [`onReset`](createAutocomplete#onreset) +- [`source.onActive`](sources#onactive) +- [`source.onSelect`](sources#onselect) +- [`source.getItems`](sources#getitems) +- `plugin.subscribe` + +The `context` object is available on the [`state`](/docs/state) object. + +### `setContext` + +> `(value: Record) => void` + +The function to pass data to to store it in the context. + +### `context` + +> `Record` + +The context to read data from. From 7560bc0d9fd2753bf39801473cab7a1c22f18350 Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Mon, 18 Jan 2021 11:09:52 +0100 Subject: [PATCH 06/22] docs: rewrote "Controlling behavior with State" (#391) --- packages/website/docs/state.md | 122 ++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 56 deletions(-) diff --git a/packages/website/docs/state.md b/packages/website/docs/state.md index ab946e486..56edb4bcb 100644 --- a/packages/website/docs/state.md +++ b/packages/website/docs/state.md @@ -3,60 +3,40 @@ id: state title: Controlling behavior with State --- -The autocomplete state drives the behavior of the experience. - -:::note Draft - -This page needs to cover: - -- State is the underlying set of properties that drives the autocomplete behavior. For example, the **query** state is the value in the input to search and retrieve items for. As the query state changes, the items retrieved and displayed from the **sources** change. -- Autocomplete state is made up of: - - query - the value in the input to search for - - selectedItemId - which item (if any) is selected - - completion - the completed version of the input text - - isOpen - if the autocomplete display panel is open - - status - 'idle' | 'loading' | 'stalled' | 'error' - - collections - **Sources** and items powering the experience - - context - global state passed to lifecycle hooks, see more in **Context** -- You can set an **initialState** when instantiating an autocomplete. - - Code snippet -- State changes occur automatically when a user changes the input, selects an item, etc. You can also manually set the state using setters - - For example, you may want to manually set the query in some cases - - Code snippet - - This is the full list of setters: - - setQuery - - setSelectedItemId - - setIsOpen - - setStatus - - setCollections - - setContext -- Finally, you can listen for state changes using **onStateChange** - - Code snippet - -::: - -The state is passed to all lifecycle hooks so that you can customize the behavior. - -## Examples +The state drives the behavior of the autocomplete experience. + +The state is an underlying set of properties that drives the autocomplete behavior. For example, `query` contains the value typed in the search input. As the query changes, the retrieved items from the [sources](/docs/sources) change. + +The state contains: +- `query`: the search input value +- `activeItemId`: which item is active +- `completion`: the completed version of the query +- `isOpen`: whether the autocomplete display panel is open or not +- `status`: the autocomplete status +- `collections`: the autocomplete's collections of items +- `context`: the global context passed to lifecycle hooks (see more in [**Context**](/docs/context)) + +## Usage + +The state is available in all lifecycle hooks so you can customize the behavior. ### Setting an initial state -You can instantiate autocomplete with an initial state with the `initialState` prop. +You can instantiate an autocomplete with an initial state via the [`initialState`](/docs/autocomplete-js/#initialstate) prop. ```js const autocomplete = createAutocomplete({ // ... initialState: { - // This sets the `search` query param as initial query. - // Example: `https://website.com/?search=navigator + // This uses the `search` query parameter as the initial query query: new URL(window.location).searchParams.get('search'), }, }); ``` -### Listening state changes +### Listening to state changes -You can create your own API based on the autocomplete state with the [`onStateChange`](createAutocomplete#onstatechange) prop. +State changes occur automatically when a user interacts with the autocomplete (updates the input text, selects an item, etc.). You can react to state changes using the [`onStateChange`](createAutocomplete#onstatechange) lifecycle hook. ```js const autocomplete = createAutocomplete({ @@ -67,51 +47,81 @@ const autocomplete = createAutocomplete({ }); ``` +You can also manually update the state using setters. It's useful to implement custom features on top of autocomplete. + +For example, let's say you want to let users fill the search input with the value of a suggestion by clicking or tapping it. You can use the [`setQuery`](state#setquery) setter provided by [`getSources`](sources#getsources) to attach an event when clicking the tap-ahead button and manually set the query. + +```js +const autocomplete = createAutocomplete({ + getSources({ query, setQuery, refresh }) { + return [ + { + // ... + templates: { + item({ item, root }) { + const tapAheadButton = document.createElement('button'); + + tapAheadButton.addEventListener('click', (event) => { + event.stopPropagation(); + + setQuery(item.query); + refresh(); + }); + + root.appendChild(tapAheadButton); + }, + }, + }, + ]; + }, +}); +``` + ## State ### `activeItemId` > `number | null` | defaults to `null` -The highlighted item index. +The highlighted item's index. ### `query` > `string` | defaults to `""` -The query of the input. +The value of the search input. ### `completion` > `string | null` | defaults to `null` -The completion of the input. +The completion of the query. ### `isOpen` > `boolean` | defaults to `false` -Whether the panel is opened. +Whether the panel is open or not. ### `collections` -> `Collection[]` | defaults to `[]` +> `AutocompleteCollection[]` | defaults to `[]` -The collections of the experience. +The collections of items. ### `status` > `'idle' | 'loading' | 'stalled' | 'error'` | defaults to `idle` -The status of the experience. +The autocomplete's status. ### `context` -> `object` | defaults to `{}` +> `AutocompleteContext` | defaults to `{}` -The autocomplete context passed to lifecycle hooks. +The global context passed to lifecycle hooks. -Learn more on the [context](context) page. +See more in [**Context**](context). ## Setters @@ -131,24 +141,24 @@ Sets the query. > `(value: boolean) => void` -Sets the open state of the panel. +Sets whether the panel is open or not. ### `setStatus` > `(value: 'idle' | 'loading' | 'stalled' | 'error') => void` -Sets the status of the experience. +Sets the status of the autocomplete. ### `setCollections` > `(value: Collection[]) => void` -Sets the collections of the experience. +Sets the collections of items of the autocomplete. ### `setContext` -> `(value: object) => void` +> `(value: AutocompleteContext) => void` -Sets the context passed in the lifecycle hooks. +Sets the context passed to lifecycle hooks. -Learn more on the [context](context) page. +See more in [**Context**](context). From 3a6e936bb43542bd2b6f8ccb9165189b49ebfd19 Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Wed, 20 Jan 2021 17:00:01 +0100 Subject: [PATCH 07/22] docs: rewrite "Populating autocomplete with Sources" (#396) --- packages/website/docs/sources.md | 335 ++++++++++++++++--------------- 1 file changed, 171 insertions(+), 164 deletions(-) diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index be7fdb937..1757e5b0d 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -3,49 +3,21 @@ id: sources title: Populating autocomplete with Sources --- -Sources are data that describes the suggestions and their behavior. - -:::note Draft - -This page needs to cover: - -- **Sources** define the data sources where to retrieve the items to display in the autocomplete. - - For example, you could hardcode an array of static items to display. - - Code snippet - - You could also search for matches into a list of hard-coded items: - - Code snippet -- Before moving on to more complex sources, let’s take a closer look at these static sources. - - You’ll notice that **getSources** returns an array of sources. - - Each source uses the **getItems** function to return the actual items to display. The items could just be a static array, or you could use a function to filter/refine the items based on the query. The **getItems** function is called each time the input changes. - - [Optional] You can use **getItemInputValue** to fill the search box input with a particular attribute of an item. By default, the input will show the same query a user has typed in. - - Code snippet - - [Optional] You can use **getItemURL** to add [keyboard accessibility](https://autocomplete.algolia.com/docs/keyboard-navigation) features to let users open items in the current tab, in a new tab or in a new window. - - Code snippet - - In addition to defining the data sources for items in the autocomplete, a source also defines how to display the items using **templates.** - - A **template** can either return a string: - - Code snippet - - Or a **template** can be a Preact component: - - Code snippet - - Or a **template** can be HTML: - - Code snippet similar to [this sandbox](https://codesandbox.io/s/algoliajs-example-forked-298f6?file=/app.tsx) - - Or a **template** can be a component using your own `createElement` and `Fragment`: - - Code snippet -- Static sources can be useful, particularly if a user hasn’t typed anything yet. (See our guide on using **dynamic sources based on the query**.) But you may want a more complex search beyond just looking for exact matches in strings. In that case, you can search into one or more Algolia indices using the built-in **getAlgoliaHits** method**:** - - **Code snippet** - - **getAlgoliaHits expects a searchClient and one or more queries (link relevant Algolia docs)** -- **getSources** support promises, which means that you can fetch your sources from an asynchronous API: - - Code snippet +Sources define where to retrieve items from and their behavior. -::: +**The most important aspect of an autocomplete experience is the items you display.** Most of the time they're search results to a query, but you could imagine many different usages. + +Autocomplete gives you total freedom to return rich suggestions via the Sources API. -## Examples +## Usage ### Using static sources -Static sources means that whatever the autocomplete state is, the same sources are always returned. +The most straightforward way to provide items is to return static sources. Each source returns a collection of items. -```ts -const autocomplete = createAutocomplete({ +```js +autocomplete({ + // ... getSources() { return [ { @@ -58,18 +30,22 @@ const autocomplete = createAutocomplete({ getItemUrl({ item }) { return item.url; }, + // ... }, ]; }, }); ``` +Here, whatever the autocomplete state is, it always returns these two items. + ### Searching in static sources -You can search within your sources to update them as the user types: +You can access the autocomplete state in your sources, meaning you can search within static sources to update them as the user types. -```ts -const autocomplete = createAutocomplete({ +```js +autocomplete({ + // ... getSources() { return [ { @@ -82,17 +58,78 @@ const autocomplete = createAutocomplete({ getItemUrl({ item }) { return item.url; }, + // ... }, ]; }, }); ``` -To bring more search capabilities, you can plug an Algolia index: +Before moving on to more complex sources, let's take a closer look at the code. + +Notice that the [`getSources`](#getsources) function returns an array of sources. Each source implements a [`getItems`](#getitems) function to return the items to display. These items can be a simple static array, but you can also use a function to refine items based on the query. **The [`getItems`](#getitems) function is called whenever the input changes.** + +By default, autocomplete items are meant to be hyperlinks. To determine what URL to navigate to, you can implement a [`getItemURL`](#getitemurl) function. It enables the [keyboard accessibility](/docs/keyboard-navigation) feature, allowing users to open items in the current tab, a new tab, or a new window from their keyboard. + +### Customizing items with templates -```ts +In addition to defining data sources for items, a source also lets you customize how to display items using [`templates`](#templates). + +```js +autocomplete({ + // ... + getSources({ query }) { + return [ + { + // ... + templates: { + item({ item }) { + return `Result: ${item.name}`; + }, + }, + }, + ]; + }, +}); +``` + +You can also display header and footer elements around the list of items. + +```js +autocomplete({ + // ... + getSources({ query }) { + return [ + { + // ... + templates: { + header() { + return 'Suggestions'; + }, + item({ item }) { + return `Result: ${item.name}`; + }, + footer() { + return 'Footer'; + }, + }, + }, + ]; + }, +}); +``` + +**Templates aren't limited to strings.** You can provide anything as long as they're a valid virtual DOM element (see more in **Templates**). + +### Using dynamic sources + +Static sources can be useful, especially [when the user hasn't typed anything yet](#mixing-static-and-dynamic-sources-based-on-the-query). However, you might want more robust search capabilities beyond exact matches in strings. + +In this case, you could search into one or more Algolia indices using the built-in [`getAlgoliaHits`](getAlgoliaHits) function from the `autocomplete-preset-algolia` preset. + +```js import algoliasearch from 'algoliasearch/lite'; -import { createAutocomplete } from '@algolia/autocomplete-core'; +import { autocomplete } from '@algolia/autocomplete-js'; import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( @@ -100,7 +137,8 @@ const searchClient = algoliasearch( '6be0576ff61c053d5f9a3225e2a90f76' ); -const autocomplete = createAutocomplete({ +autocomplete({ + // ... getSources() { return [ { @@ -118,21 +156,26 @@ const autocomplete = createAutocomplete({ getItemUrl({ item }) { return item.url; }, + // ... }, ]; }, }); ``` -You can notice that `getItems` supports [promises](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise). +:::note + +The [`getItems`](#getitems) function supports [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), meaning you can plug it to any asynchronous API. -### Using dynamic sources based on query +::: + +### Mixing static and dynamic sources based on the query -A common pattern is to display a different source when the query is empty, and when the user started typing. +You don't have to show an empty screen until the user types a query. A typical pattern is to display a different source when the query is empty and switch once the user starts typing. -```ts +```js import algoliasearch from 'algoliasearch/lite'; -import { createAutocomplete } from '@algolia/autocomplete-core'; +import { autocomplete } from '@algolia/autocomplete-js'; import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( @@ -140,10 +183,11 @@ const searchClient = algoliasearch( '6be0576ff61c053d5f9a3225e2a90f76' ); -const autocomplete = createAutocomplete({ +autocomplete({ + // ... getSources({ query }) { if (!query) { - [ + return [ { getItems() { return [ @@ -180,17 +224,51 @@ const autocomplete = createAutocomplete({ }); ``` -You can notice that we can **get the `query` from the `getSources` function to conditionally return sources**. +The [`getSources`](#getsources) function provides access to the current `query`, which you can use to return sources conditionally. You can use this pattern to display recent searches when the query is empty and search results when the user types. -This pattern can be extended to display recent searches on empty query, and search results when a query is typed. You can compute dynamic sources based on the complete autocomplete state, not only the query. +Note that you have access to the [full autocomplete state](state), not only the query. It lets you compute sources based on [various aspects](state#state), such as the query, but also the autocomplete status, whether the autocomplete is open or not, the context, etc. ### Using asynchronous sources -`getSources` also supports promises, which means that you can fetch your sources from an asynchronous API: +The [`getSources`](#getsources) function supports promises so that you can fetch sources from any asynchronous API. It can be Algolia or any third-party API you can query with an HTTP request. + +For example, you could use the [Query Autocomplete](https://developers.google.com/places/web-service/query) service of the Google Places API to search for places and retrieve popular queries that map to actual points of interest. + +```js +autocomplete({ + // ... + getSources({ query }) { + return fetch( + `https://maps.googleapis.com/maps/api/place/queryautocomplete/json?input=${query}&key=YOUR_GOOGLE_PLACES_API_KEY` + ) + .then((response) => response.json()) + .then(({ predictions }) => { + return [ + { + getItems() { + return predictions; + }, + getItemInputValue({ item }) { + return item.description; + }, + }, + ]; + }); + }, +}); +``` + +Note the usage of the [`getItemInputValue`](#getiteminputvalue) function to return the value of the item. It lets you fill the search box with a new value whenever the user selects an item, allowing them to refine their query and retrieve more relevant results. + +### Using multiple sources + +An autocomplete experience doesn't have to return only a single set of results. Autocomplete lets you fetch from different sources and display different types of results that serve different purposes. -```ts +For example, you may want to display Algolia search results and Query Suggestions based on the current query to let users refine it and yield better results. + +```js import algoliasearch from 'algoliasearch/lite'; -import { createAutocomplete } from '@algolia/autocomplete-core'; +import { autocomplete } from '@algolia/autocomplete-js'; import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia'; const searchClient = algoliasearch( @@ -198,9 +276,10 @@ const searchClient = algoliasearch( '6be0576ff61c053d5f9a3225e2a90f76' ); -const autocomplete = createAutocomplete({ +autocomplete({ + // ... getSources({ query }) { - return getAlgoliaResults({ + return getAlgoliaHits({ searchClient, queries: [ { @@ -213,14 +292,16 @@ const autocomplete = createAutocomplete({ }, ], }).then((results) => { - const [querySuggestions, products] = results; + const [suggestions, products] = results; return [ { getItems() { - return querySuggestions.hits; + return suggestions.hits; + }, + getItemInputValue() { + return item.query; }, - getItemInputValue: ({ item }) => item.query, }, { getItems() { @@ -236,25 +317,33 @@ const autocomplete = createAutocomplete({ }); ``` -This pattern can be used to store data in the [context](context) before returning the sources. +:::note -## Reference +You can use the official [`autocomplete-plugin-query-suggestions`](createQuerySuggestionsPlugin) plugin to retrieve Query Suggestions from Algolia. + +::: + +## Sources ### `getSources` > `(params: { query: string, state: AutocompleteState, ...setters: Autocomplete Setters }) => Array | Promise>` -The function to fetch the sources and their behaviors. +The function to fetch sources and their behaviors. + +See [source](#source) for what to return. -A source is described by the following properties: +## Source + +Each source implements the following interface: ### `getItems` > `(params: { query: string, state: AutocompleteState, ...setters }) => Suggestion[] | Promise` | **required** -Called when the input changes. You can use this function to filter/search the items based on the query. +Called when the input changes. You can use this function to filter the items based on the query. -```ts +```js const items = [{ value: 'Apple' }, { value: 'Banana' }]; const source = { @@ -271,13 +360,13 @@ const source = { Called to get the value of the item. The value is used to fill the search box. -If you do not wish to update the input value when an item is selected, you can return `state.query`. - -```ts +```js const items = [{ value: 'Apple' }, { value: 'Banana' }]; const source = { - getItemInputValue: ({ item }) => item.value, + getItemInputValue({ item }) { + return item.value; + }, // ... }; ``` @@ -286,9 +375,9 @@ const source = { > `(params: { item: Item, state: AutocompleteState }) => string | undefined` -Called to get the URL of the item. The value is used to add [keyboard accessibility](keyboard-navigation) features to allow to open items in the current tab, in a new tab or in a new window. +Called to get the URL of the item. The value is used to add [keyboard accessibility](keyboard-navigation) features to let users open items in the current tab, a new tab, or a new window. -```ts +```js const items = [ { value: 'Google', url: 'https://google.com' }, { value: 'Amazon', url: 'https://amazon.com' }, @@ -306,102 +395,20 @@ const source = { > `(params: { state: AutocompleteState, ...setters, event: Event, item: TItem, itemInputValue: string, itemUrl: string, source: AutocompleteSource }) => void` | defaults to `({ setIsOpen }) => setIsOpen(false)` -Called when an item is selected. +Called whenever an item is selected. ### `onActive` > `(params: { state: AutocompleteState, ...setters, event: Event, item: TItem, itemInputValue: string, itemUrl: string, source: AutocompleteSource }) => void` -Called when an item is active. - -You can trigger different behaviors if the item is active following a mouse event or a keyboard event based on the `event` param. - -### `templates` (specific to `@algolia/autocomplete-js`) - -> `SourceTemplate` - -The `@algolia/autocomplete-js` supports source templates. - -A template can either return a string, or perform DOM mutations (manipulating DOM elements with JavaScript and attaching events) without returning a string. - -```ts title="SourceTemplate" -type SourceTemplate = { - item: Template<{ - root: HTMLElement; - item: TItem; - state: AutocompleteState; - }>; - header?: Template<{ - root: HTMLElement; - state: AutocompleteState; - source: AutocompleteSource; - items: TItem[]; - }>; - footer?: Template<{ - root: HTMLElement; - state: AutocompleteState; - source: AutocompleteSource; - items: TItem[]; - }>; - empty?: Template<{ - root: HTMLElement; - state: AutocompleteState; - source: AutocompleteSource; - }>; -}; -``` +Called whenever an item is active. -```ts title="Example" -import algoliasearch from 'algoliasearch/lite'; -import { - autocomplete, - getAlgoliaHits, - reverseHighlightHit, -} from '@algolia/autocomplete-js'; +You can trigger different behaviors if the item is active depending on the triggering event using the `event` parameter. -const searchClient = algoliasearch( - 'latency', - '6be0576ff61c053d5f9a3225e2a90f76' -); +### `templates` -const autocompleteSearch = autocomplete({ - container: '#autocomplete', - getSources() { - return [ - { - getItemInputValue({ item }) { - return item.query; - }, - getItems({ query }) { - return getAlgoliaHits({ - searchClient, - queries: [ - { - indexName: 'instant_search_demo_query_suggestions', - query, - params: { - hitsPerPage: 4, - }, - }, - ], - }); - }, - templates: { - header() { - return 'Suggestions'; - }, - item({ item }) { - return reverseHighlightHit({ hit: item, attribute: 'query' }); - }, - footer() { - return 'Footer'; - }, - empty() { - return 'No results'; - }, - }, - }, - ]; - }, -}); -``` +> `AutocompleteTemplates` + +A set of templates to customize how items are displayed. + +You can also provide templates for header and footer elements around the list of items. From c1c8ddbd35bcb4c49a99cf5d04bb07d9521dc1ea Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Thu, 21 Jan 2021 13:23:37 +0100 Subject: [PATCH 08/22] docs: write "Displaying items with Templates" (#397) --- packages/website/docs/sources.md | 2 +- packages/website/docs/templates.md | 306 +++++++++++++++++++++++++++++ packages/website/sidebars.js | 1 + 3 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 packages/website/docs/templates.md diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index 1757e5b0d..371bf8723 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -119,7 +119,7 @@ autocomplete({ }); ``` -**Templates aren't limited to strings.** You can provide anything as long as they're a valid virtual DOM element (see more in **Templates**). +**Templates aren't limited to strings.** You can provide anything as long as they're a valid virtual DOM element (see more in [**Templates**](templates)). ### Using dynamic sources diff --git a/packages/website/docs/templates.md b/packages/website/docs/templates.md new file mode 100644 index 000000000..ce1908a5c --- /dev/null +++ b/packages/website/docs/templates.md @@ -0,0 +1,306 @@ +--- +id: templates +title: Displaying items with Templates +--- + +Templates let you customize the display of your autocomplete's items. + +Once you've set up your [data sources](sources), you need to define how they display in your autocomplete experience. It encompasses the structure for each item and the way they look. + +Autocomplete provides a Templates API to let you fully customize the render of each item. + +## Usage + +### Rendering each item + +The rendering system of Autocomplete uses an agnostic virtual DOM implementation. You can return anything from each template as long as they're valid virtual DOM elements (VNodes). + +For example, templates can return a string: + +```js +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item }) { + return `Result: ${item.name}`; + }, + }, + }, + ]; + }, +}); +``` + +Or a [Preact](https://preactjs.com/) component: + +```jsx +/** @jsx h */ +import { h } from 'preact'; +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item }) { + return
{item.name}
; + }, + }, + }, + ]; + }, +}); +``` + +:::info + +Autocomplete uses [Preact 10](https://preactjs.com/guide/v10/whats-new/) to render templates by default. It isn't compatible with earlier versions. + +::: + +### Returning HTML + +Native HTML elements aren't valid VNodes, which means you can't return a template string that contains HTML, or an HTML element. But if you're not using a virtual DOM implementation in your app, there are still two ways you can return HTML. + +#### Using `createElement` + +Each template function provides access to `createElement` and `Fragment` to create VNodes. + +```js +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item, createElement, Fragment }) { + return createElement(Fragment, {}, item.name); + }, + }, + }, + ]; + }, +}); +``` + +You can also leverage `dangerouslySetInnerHTML` if you need to provide more complex HTML without having to create nested VNodes. + +```js +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item, createElement }) { + return createElement('div', { + dangerouslySetInnerHTML: { + __html: `
+ ${item.name} +
+
+ ${item.name} +
`, + }, + }); + }, + }, + }, + ]; + }, +}); +``` + +By default, `createElement` and `Fragment` default to [Preact](https://preactjs.com/)'s `preact.createElement` (or `h`) and `preact.Fragment`. You can customize these and provide the virtual DOM implementation you prefer. + +#### Using the `htm` library + +If you're not using a transpiler to build your app, you can still use Autocomplete with the [htm](https://github.com/developit/htm) library, which lets you use a JSX-like syntax directly in the browser. + +```js +import { html } from 'htm/preact'; +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item }) { + return html`
${item.name}
` + }, + }, + }, + ]; + }, +}); +``` + +### Rendering a header and footer + +In addition to rendering items, you can customize what to display before and after the list of items using the [`header`](#header) and [`footer`](#footer) templates. + +```js +autocomplete({ + // ... + getSources({ query }) { + return [ + { + // ... + templates: { + header() { + return 'Suggestions'; + }, + item({ item }) { + return `Result: ${item.name}`; + }, + footer() { + return 'Footer'; + }, + }, + }, + ]; + }, +}); +``` + +### Rendering an empty state + +When there are no results, you might want to display a message to inform users or let them know what to do next. You can do this with the [`empty`](#empty) template. + +```js +autocomplete({ + // ... + getSources({ query }) { + return [ + { + // ... + templates: { + // ... + empty() { + return 'No results.'; + }, + }, + }, + ]; + }, +}); +``` + +### Styling items + +Since you're fully controlling the rendered HTML, you can style it the way you want or use any class-based CSS library. + +For example, if you're using [Bootstrap](https://getbootstrap.com/): + +```jsx +/** @jsx h */ +import { h } from 'preact'; +import { autocomplete } from '@algolia/autocomplete-js'; + +import 'https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item }) { + return
  • {item.name}
  • ; + }, + }, + }, + ]; + }, +}); +``` + +Or [Tailwind CSS](https://tailwindcss.com/): + +```jsx +/** @jsx h */ +import { h } from 'preact'; +import { autocomplete } from '@algolia/autocomplete-js'; + +import 'https://unpkg.com/tailwindcss@latest/dist/tailwind.min.css'; + +autocomplete({ + // ... + getSources() { + return [ + { + // ... + templates: { + item({ item }) { + return ( +
  • + {item.name} +
  • + ); + }, + }, + }, + ]; + }, +}); +``` + +## Templates + +### `templates` + +> `AutocompleteTemplates` + +A set of templates to customize how items are displayed. You can also provide templates for header and footer elements around the list of items. + +You must define `templates` within your [sources](sources). + +See [template](#template) for what to return. + +## Template + +### `header` + +> `(params: { state: AutocompleteState, source: AutocompleteSource, items: TItem[], createElement: Pragma, Fragment: PragmaFrag }) => VNode | string` + +A function that returns the template for the header (before the list of items). + +### `item` + +> `(params: { item: TItem, state: AutocompleteState, createElement: Pragma, Fragment: PragmaFrag }) => VNode | string` + +A function that returns the template for each item of the source. + +### `footer` + +> `(params: { state: AutocompleteState, source: AutocompleteSource, items: TItem[], createElement: Pragma, Fragment: PragmaFrag }) => VNode | string` + +A function that returns the template for the footer (after the list of items). + +### `empty` + +> `(params: { state: AutocompleteState, source: AutocompleteSource, createElement: Pragma, Fragment: PragmaFrag }) => VNode | string` + +A function that returns the template for when there are no items. diff --git a/packages/website/sidebars.js b/packages/website/sidebars.js index ed22167ed..5ac4b06e7 100644 --- a/packages/website/sidebars.js +++ b/packages/website/sidebars.js @@ -6,6 +6,7 @@ module.exports = { 'Core Concepts': [ 'basic-options', 'sources', + 'templates', 'state', 'context', 'keyboard-navigation', From fbfca35e99002c27d3b79f79aa91a10fde2bdf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= <20689156+shortcuts@users.noreply.github.com> Date: Wed, 20 Jan 2021 13:43:58 +0100 Subject: [PATCH 09/22] feat(emptyStates): implements empty source template and renderEmpty method (#395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implements `empty` template and `renderEmpty` method * Add wait function to `test/utils` folder Co-authored-by: François Chalifour --- examples/js/app.ts | 94 +++++++++++++++++++ .../src/__tests__/autocomplete.test.ts | 7 +- packages/autocomplete-js/src/autocomplete.ts | 10 ++ test/utils/index.ts | 1 + test/utils/wait.ts | 5 + 5 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 examples/js/app.ts create mode 100644 test/utils/wait.ts diff --git a/examples/js/app.ts b/examples/js/app.ts new file mode 100644 index 000000000..14402393e --- /dev/null +++ b/examples/js/app.ts @@ -0,0 +1,94 @@ +import { + autocomplete, + getAlgoliaHits, + reverseHighlightHit, +} from '@algolia/autocomplete-js'; +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; +import algoliasearch from 'algoliasearch'; +import insightsClient from 'search-insights'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); +const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ + key: 'search', + limit: 3, +}); +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return recentSearchesPlugin.data.getAlgoliaSearchParams({ + clickAnalytics: true, + }); + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + plugins: [ + algoliaInsightsPlugin, + recentSearchesPlugin, + querySuggestionsPlugin, + ], + getSources({ query }) { + if (!query) { + return []; + } + + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [{ indexName: 'instant_search', query }], + }); + }, + templates: { + item({ item, root }) { + const itemContent = document.createElement('div'); + const ItemSourceIcon = document.createElement('div'); + const itemTitle = document.createElement('div'); + const sourceIcon = document.createElement('img'); + + sourceIcon.width = 20; + sourceIcon.height = 20; + sourceIcon.src = item.image; + + ItemSourceIcon.classList.add('aa-ItemSourceIcon'); + ItemSourceIcon.appendChild(sourceIcon); + + itemTitle.innerHTML = reverseHighlightHit({ + hit: item, + attribute: 'name', + }); + itemTitle.classList.add('aa-ItemTitle'); + + itemContent.classList.add('aa-ItemContent'); + itemContent.appendChild(ItemSourceIcon); + itemContent.appendChild(itemTitle); + + root.appendChild(itemContent); + }, + empty({ root }) { + const itemContent = document.createElement('div'); + + itemContent.innerHTML = 'No results for this query'; + itemContent.classList.add('aa-ItemContent'); + + root.appendChild(itemContent); + }, + }, + }, + ]; + }, +}); diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 23035ae68..240359ddb 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -1,5 +1,6 @@ import { fireEvent, waitFor } from '@testing-library/dom'; +import { wait } from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; describe('autocomplete-js', () => { @@ -92,8 +93,8 @@ describe('autocomplete-js', () => { stroke-dasharray="164.93361431346415 56.97787143782138" stroke-width="6" > - - + + { type="rotate" values="0 50 50;90 50 50;180 50 50;360 50 50" /> - + diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index fdd248d52..bcc026da5 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -150,6 +150,16 @@ export function autocomplete( props.value.renderer.renderEmpty) || props.value.renderer.render; + hasEmptySourceTemplateRef.current = renderProps.state.collections.some( + (collection) => collection.source.templates.empty + ); + + const render = + (!getItemsCount(renderProps.state) && + !hasEmptySourceTemplateRef.current && + props.value.renderer.renderEmpty) || + props.value.renderer.render; + renderSearchBox(renderProps); renderPanel(render, renderProps); } diff --git a/test/utils/index.ts b/test/utils/index.ts index 912a9c3f6..551720c4d 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -8,3 +8,4 @@ export * from './createSource'; export * from './createState'; export * from './defer'; export * from './runAllMicroTasks'; +export * from './wait'; diff --git a/test/utils/wait.ts b/test/utils/wait.ts new file mode 100644 index 000000000..a68b7baa2 --- /dev/null +++ b/test/utils/wait.ts @@ -0,0 +1,5 @@ +export function wait(time: number) { + return new Promise((resolve) => { + setTimeout(resolve, time); + }); +} From 30f89493272a6383118bec26fd9da5ab95557e4e Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Thu, 21 Jan 2021 16:59:10 +0100 Subject: [PATCH 10/22] fix: fix code example (#403) --- packages/website/docs/sources.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/website/docs/sources.md b/packages/website/docs/sources.md index 371bf8723..6820452a5 100644 --- a/packages/website/docs/sources.md +++ b/packages/website/docs/sources.md @@ -53,7 +53,9 @@ autocomplete({ return [ { label: 'Twitter', url: 'https://twitter.com' }, { label: 'GitHub', url: 'https://github.com' }, - ].filter((item) => item.toLowerCase().includes(query.toLowerCase())); + ].filter(({ label }) => + label.toLowerCase().includes(query.toLowerCase()) + ); }, getItemUrl({ item }) { return item.url; From e4456fb2690ceb8decc2f618cca796fa6c54c15a Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Thu, 21 Jan 2021 17:43:40 +0100 Subject: [PATCH 11/22] docs: switch core usage with autocomplete-js (#401) --- packages/website/docs/context.md | 2 +- packages/website/docs/keyboard-navigation.md | 6 ++- packages/website/docs/state.md | 42 +++++++++++++------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/packages/website/docs/context.md b/packages/website/docs/context.md index e75b63807..0305d479b 100644 --- a/packages/website/docs/context.md +++ b/packages/website/docs/context.md @@ -16,7 +16,7 @@ Context exposes a `setContext` function, which takes an object and merges it wit The following example stores the number of hits from an Algolia response, making it accessible everywhere in your autocomplete. ```js -const autocomplete = createAutocomplete({ +autocomplete({ // ... getSources({ query, setContext }) { return getAlgoliaResults({ diff --git a/packages/website/docs/keyboard-navigation.md b/packages/website/docs/keyboard-navigation.md index 51818ec2b..3dea54faf 100644 --- a/packages/website/docs/keyboard-navigation.md +++ b/packages/website/docs/keyboard-navigation.md @@ -20,7 +20,7 @@ The Navigator API defines three navigation schemes based on key combinations: To activate keyboard navigation, you need to implement a [`getItemUrl`](createAutocomplete#getitemurl) function in each of your [sources](/docs/sources) to provide the URL to navigate to. It tells the Navigator API which link to open on Enter. ```js {6-8} -const autocomplete = createAutocomplete({ +autocomplete({ // ... getSources() { return [ @@ -59,8 +59,10 @@ For example, if you're using Autocomplete in a [Gatsby](https://www.gatsbyjs.org ```js import { navigate } from 'gatsby'; +import { autocomplete } from '@algolia/autocomplete-js'; -const autocomplete = createAutocomplete({ +autocomplete({ + // ... navigator: { navigate({ itemUrl }) { navigate(itemUrl); diff --git a/packages/website/docs/state.md b/packages/website/docs/state.md index 56edb4bcb..0705c524a 100644 --- a/packages/website/docs/state.md +++ b/packages/website/docs/state.md @@ -25,7 +25,7 @@ The state is available in all lifecycle hooks so you can customize the behavior. You can instantiate an autocomplete with an initial state via the [`initialState`](/docs/autocomplete-js/#initialstate) prop. ```js -const autocomplete = createAutocomplete({ +autocomplete({ // ... initialState: { // This uses the `search` query parameter as the initial query @@ -39,7 +39,7 @@ const autocomplete = createAutocomplete({ State changes occur automatically when a user interacts with the autocomplete (updates the input text, selects an item, etc.). You can react to state changes using the [`onStateChange`](createAutocomplete#onstatechange) lifecycle hook. ```js -const autocomplete = createAutocomplete({ +autocomplete({ // ... onStateChange({ state }) { console.log(state); @@ -52,23 +52,35 @@ You can also manually update the state using setters. It's useful to implement c For example, let's say you want to let users fill the search input with the value of a suggestion by clicking or tapping it. You can use the [`setQuery`](state#setquery) setter provided by [`getSources`](sources#getsources) to attach an event when clicking the tap-ahead button and manually set the query. ```js -const autocomplete = createAutocomplete({ - getSources({ query, setQuery, refresh }) { +/** @jsx h */ +import { h } from 'preact'; +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + // ... + getSources({ setQuery, refresh }) { return [ { // ... templates: { - item({ item, root }) { - const tapAheadButton = document.createElement('button'); - - tapAheadButton.addEventListener('click', (event) => { - event.stopPropagation(); - - setQuery(item.query); - refresh(); - }); - - root.appendChild(tapAheadButton); + item({ item }) { + return ( +
    +
    Icon
    +
    {item.query}
    + +
    + ); }, }, }, From 3f602726f8d1ee8d8ba5c69af94755ae30ec71b4 Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Tue, 26 Jan 2021 14:04:57 +0100 Subject: [PATCH 12/22] docs: write "Basic configuration options" (#402) --- packages/website/docs/basic-options.md | 62 +++++++++++++++++++++----- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/packages/website/docs/basic-options.md b/packages/website/docs/basic-options.md index 95c7f513a..b4b198fcf 100644 --- a/packages/website/docs/basic-options.md +++ b/packages/website/docs/basic-options.md @@ -3,18 +3,56 @@ id: basic-options title: Basic configuration options --- -:::note Draft +Everything you need to create fantastic autocomplete experiences. -This page needs to cover: +We've built Autocomplete to give you unlimited flexibility while freeing you from having to think about things like keyboard navigation, accessibility, or UI state. **The library offers a wide variety of APIs to let you fully customize the behavior and rendering of your autocomplete.** -- There are only two required params to create an autocomplete: - - You need to provide a selector for the **container** you want autocomplete to appear in. - - You need to define where to get the items to display using **getSources** (or a **plug-in** which provides **getSources**). Check our **Sources** core concept for more information. -- Beyond this, there are many parameters you can use to customize the experience and help you with development. Here are some commonly used ones: - - Use **placeholder** to define the text that appears in the input when a user hasn’t typed anything. - - Use **autoFocus** to focus on the search box when the page is loaded and **openOnFocus** to display items as soon as a user selects the autocomplete (without typing anything). - - Use the **onStateChange** hook to call a function whenever the state changes (see our **State** core concept for more info). - - Use **`debug: true`** to keep the autocomplete panel with items open, even when the blur event occurs. (This is only meant to be used during development. See our **Debugging guide** for more info.) -- Check out the **API reference** for a full list of params. +Yet, only two parameters are required to create an autocomplete: +- The **container** you want your autocomplete to go in. +- The **sources** from which to get the items to display (see more in [**Sources**](sources)). -::: +```js title="JavaScript" +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + container: '#autocomplete', + getSources() { + return [ + { + getItems({ query }) { + return [ + { label: 'Twitter', url: 'https://twitter.com' }, + { label: 'GitHub', url: 'https://github.com' }, + ].filter(({ label }) => + label.toLowerCase().includes(query.toLowerCase()) + ); + }, + getItemUrl({ item }) { + return item.url; + }, + templates: { + item({ item }) { + return item.label; + }, + }, + }, + ]; + }, +}); +``` + +The `container` options refers to where to inject the autocomplete in your HTML. It can be a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) or an [Element](https://developer.mozilla.org/docs/Web/API/HTMLElement). Make sure to provide a container (e.g., a `div`), not an `input`. Autocomplete generates a fully accessible search box for you. + +```html title="HTML" +
    +``` + +This is all you need to build a [fully functional, accessible, keyboard-navigable autocomplete](https://codesandbox.io/s/vigilant-dew-g2ezl). + +Now, this is a great start, but **you can go much further**. Here are some of the options you'll probably want to use next: +- Use [`placeholder`](autocomplete-js#placeholder) to define the text that appears in the input before the user types anything. +- Use [`autoFocus`](autocomplete-js#autofocus) to focus on the search box on page load, and [`openOnFocus`](autocomplete-js#openonfocus) to display items as soon as a user selects the autocomplete, even without typing. +- Use the [`onStateChange`](autocomplete-js#onstatechange) lifecycle hook to execute code whenever the state changes. +- Use [`debug: true`](autocomplete-js#debug) to keep the autocomplete panel open even when the blur event occurs (see [**Debugging**](debugging)). + +For a full list of all available parameters, check out the [API reference](autocomplete-js). From ac1bd1cdb96c819f192b6d9252223edf4966071f Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Tue, 26 Jan 2021 15:07:16 -0700 Subject: [PATCH 13/22] docs: write "help and discussion" page (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add help page * Apply suggestions from code review Co-authored-by: François Chalifour Co-authored-by: François Chalifour --- packages/website/docs/help.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/website/docs/help.md b/packages/website/docs/help.md index 0d04e6ab9..c2b02d871 100644 --- a/packages/website/docs/help.md +++ b/packages/website/docs/help.md @@ -3,12 +3,11 @@ id: help title: Help and Discussion --- - -:::note Draft -This page needs to cover: +Autocomplete is an open source library that welcomes questions, feature requests, bug reports, and contributions on [GitHub](https://github.com/algolia/autocomplete.js/tree/next). -- Ask questions and discuss with other community members on [GitHub](https://github.com/algolia/autocomplete.js/discussions/new) -- Request a feature to add to Autocomplete on [GitHub](https://github.com/algolia/autocomplete.js/discussions/new) -- Report a bug on [GitHub](https://github.com/algolia/autocomplete.js/issues/new?template=Bug_report.md) +The best way to get in touch with the maintainers and other community members of the Autocomplete library is through the [GitHub repository](https://github.com/algolia/autocomplete.js/tree/next): +- Ask questions, discuss with other community members, or request features on [GitHub Discussions](https://github.com/algolia/autocomplete.js/discussions/new). +- Report bugs using [issues](https://github.com/algolia/autocomplete.js/issues/new?template=Bug_report.md). +- Contribute directly by [submitting a pull request](https://github.com/algolia/autocomplete.js/compare). Please read the [contribution guidelines](https://github.com/algolia/autocomplete.js/blob/next/CONTRIBUTING.md) first. -::: +You're also welcome to engage with community members in our [forum](https://discourse.algolia.com/tag/autocomplete). From f636e42825c0e54176ef8a9020c2d040c9b1e795 Mon Sep 17 00:00:00 2001 From: Sarah Dayan Date: Fri, 29 Jan 2021 18:19:43 +0100 Subject: [PATCH 14/22] docs: write "Plugins" core concept (#414) --- packages/website/docs/plugins.md | 249 +++++++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 13 deletions(-) diff --git a/packages/website/docs/plugins.md b/packages/website/docs/plugins.md index 3ffe9c8d7..1ed99b47d 100644 --- a/packages/website/docs/plugins.md +++ b/packages/website/docs/plugins.md @@ -3,21 +3,244 @@ id: plugins title: Plugins --- -:::note Draft +Plugins encapsulate and distribute custom Autocomplete behaviors. -This page needs to cover: +An autocomplete can be much more than a functional combo box. **Autocomplete lets you extend and encapsulate custom behavior with its Plugin API.** -- Plugins are a way of encapsulating sources, including the data and display. They can also trigger particular actions on an interaction. -- For example, the insights plugin sends click and conversion events. -- We provide some out-of-the-box plugins, but you can also build your own. -- As a simple example, you could create a plugin to display a static list of predefined items: - - Code snippet -- We also have created a couple out-of-the box plugins: - - Our **recent-searches plug-in** stores recently made searches in local storage so that you can incorporate an individual’s recent searches into an autocomplete. - - Code snippet + live example - - Our **query-suggestions plug-in** is made to work with an Algolia Query Suggestions index. - - Code snippet + live example +For example, the [official Algolia Insights plugin](createAlgoliaInsightsPlugin) automatically sends click and conversion events to the [Algolia Insights API](https://www.algolia.com/doc/rest-api/insights/) whenever a user interacts with the autocomplete. -We'll also need to document the Plugin API. +You can use one of the existing official plugins or build your own. + +## Usage + +### Using an Autocomplete plugin + +When using an Autocomplete plugin, all you need to do is provide it via the `plugins` option. + +For example, when using the Insights plugin, you can instantiate the plugin, then pass it down to your Autocomplete instance. + +```js {11,15} +import algoliasearch from 'algoliasearch/lite'; +import { autocomplete } from '@algolia/autocomplete-js'; +import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; +import insightsClient from 'search-insights'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); +insightsClient('init', { appId, apiKey }); + +const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); + +autocomplete({ + // ... + plugins: [algoliaInsightsPlugin], +}); +``` +:::note + +Plugins execute sequentially, in the order you define them. ::: + +### Building your own plugin + +An Autocomplete plugin is an object that implements the `AutocompletePlugin` interface. + +It can [provide sources](sources), react to [state changes](state), and hook into various autocomplete lifecycle steps. It has access to setters, including the [Context API](context), allowing it to store and retrieve arbitrary data at any time. + +Let's create a plugin that searches into a static list of GitHub repositories. + +```js +const gitHubReposPlugin = { + getSources() { + return [ + { + getItems() { + return [ + { name: 'algolia/autocomplete.js', stars: 1237 }, + { name: 'algolia/algoliasearch-client-javascript', stars: 884 }, + { name: 'algolia/algoliasearch-client-php', stars: 554 }, + ].filter(({ label }) => + label.toLowerCase().includes(query.toLowerCase()) + ); + }, + getItemUrl({ item }) { + return `https://github.com/algolia/${item.name}`; + }, + templates: { + item({ item }) { + const stars = new Intl.NumberFormat('en-US').format(item.stars); + + return `${item.name} (${stars} stars)`; + }, + empty() { + return 'No results.'; + }, + }, + }, + ]; + }, +}; + +autocomplete({ + // ... + plugins: [gitHubReposPlugin], +}); +``` + +If you want to package and distribute your plugin for other people to use, you might want to expose a function instead. For example, you can use the [GitHub API](https://docs.github.com/en/rest/reference/search#search-repositories) to search into all repositories, and let people pass [API parameters](https://docs.github.com/en/rest/reference/search#search-repositories--parameters) as plugin options. + +```js title="createGitHubReposPlugin.js" +import qs from 'qs'; +import unfetch from 'unfetch'; +import debounce from 'debounce-promise'; + +const debouncedFetch = debounce(unfetch, 300); + +export function createGitHubReposPlugin(options) { + return { + getSources({ query }) { + const endpoint = [ + 'https://api.github.com/search/repositories', + qs.stringify({ ...options, q: query }), + ].join('?'); + + return debouncedFetch(endpoint) + .then((response) => response.json()) + .then((repositories) => { + return [ + { + getItems() { + return repositories.items; + }, + getItemUrl({ item }) { + return item.html_url; + }, + templates: { + item({ item }) { + const stars = new Intl.NumberFormat('en-US').format( + item.stargazers_count + ); + + return `${item.full_name} (${stars} stars)`; + }, + empty() { + return 'No results.'; + }, + }, + }, + ]; + }); + }, + }; +} +``` + +```js title="index.js" +import { autocomplete } from '@algolia/autocomplete-js'; +import { createGitHubReposPlugin } from './createGitHubReposPlugin'; + +const gitHubReposPlugin = createGitHubReposPlugin({ + per_page: 10, +}); + +autocomplete({ + container: '#autocomplete', + plugins: [gitHubReposPlugin], +}); +``` + +You can see [this demo live on CodeSandbox](https://codesandbox.io/s/amazing-neumann-d3l1p). + +:::note + +The GitHub Search API is [rate limited](https://docs.github.com/en/rest/reference/search), which means you need to debounce calls to avoid 403 errors. For instant search results with no rate limiting, highlighted results, flexible custom ranking, and more, you can index repositories into [Algolia](https://www.algolia.com/) instead. + +::: + +#### Subscribing to source lifecycle hooks + +When building, you also get access to the [`subscribe`](#subscribe) method. It runs once when the autocomplete instance starts and lets you subscribe to lifecycle hooks and interact with the instance's state and context. + +For example, let's say you want to build a plugin that sends events to Google Analytics when the user navigates results. You can use `subscribe` to hook into `onSelect` and `onActive` events and use the [Google Analytics API](https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits) there. + +```js +function createGoogleAnalyticsPlugin({ trackingId, options }) { + return { + subscribe({ onSelect, onActive }) { + ga('create', trackingId, ...options); + + const event = { + hitType: 'event', + eventCategory: 'Autocomplete', + eventLabel: item.name, + }; + + onSelect(({ item }) => { + ga('send', { + ...event, + eventAction: 'select', + }); + }); + + onActive(({ item }) => { + ga('send', { + ...event, + eventAction: 'active', + }); + }); + }, + }; +} +``` + +### Official plugins + +There are a few useful official plugins you can already use with Autocomplete. + +- [`recent-searches`](createRecentSearchesPlugin): display a list of the latest searches the user made. It comes with a [pre-implemented version](createLocalStorageRecentSearchesPlugin) that connects with the user's [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). +- [`query-suggestions`](createQuerySuggestionsPlugin): plug [Algolia Query Suggestions](https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/query-suggestions/js/) to your autocomplete. +- [`algolia-insights`](createAlgoliaInsightsPlugin): automatically send click and conversion events to the [Algolia Insights API](https://www.algolia.com/doc/rest-api/insights/) whenever a user interacts with the autocomplete. + +## Reference + +### `subscribe` + +> `(params: { onSelect: (fn: params: TParams) => void, onActive: (fn: params: TParams) => void, ...setters: AutocompleteSetters }) => void` + +The function called when Autocomplete starts. + +It lets you subscribe to lifecycle hooks and interact with the instance's state and context. + +### `onStateChange` + +> `(params: { state: AutocompleteState }) => void` + +The function called when the internal state changes. + +### `onSubmit` + +> `(params: { state: AutocompleteState, event: Event, ...setters: AutocompleteSetters }) => void` + +The function called when the Autocomplete form is submitted. + +### `onReset` + +> `(params: { state: AutocompleteState, event: Event, ...setters: AutocompleteSetters }) => void` + +The function called when the Autocomplete form is reset. + +### `getSources` + +> `(params: { query: string, state: AutocompleteState, ...setters: AutocompleteSetters }) => Array | Promise>` + +The sources to get the suggestions from. + +When defined, they're merged with the sources of your Autocomplete instance. + +### `data` + +> `unknown` + +An extra plugin object to expose properties and functions as APIs. From 9d25fc4edd02c43d93b9b365acc07e90b7945976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= <20689156+shortcuts@users.noreply.github.com> Date: Wed, 20 Jan 2021 13:43:58 +0100 Subject: [PATCH 15/22] feat(emptyStates): implements empty source template and renderEmpty method (#395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implements `empty` template and `renderEmpty` method * Add wait function to `test/utils` folder Co-authored-by: François Chalifour --- examples/js/createTagPlugin.tsx | 103 ++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 examples/js/createTagPlugin.tsx diff --git a/examples/js/createTagPlugin.tsx b/examples/js/createTagPlugin.tsx new file mode 100644 index 000000000..f29c8f541 --- /dev/null +++ b/examples/js/createTagPlugin.tsx @@ -0,0 +1,103 @@ +/** @jsx h */ +import { AutocompletePlugin } from '@algolia/autocomplete-js'; +import { h, render } from 'preact'; + +type CreateTagPluginProps = { + sourceIds?: string[]; + getTagLabel({ item }: { item: TItem }): any; + onTag({ item }: { item: TItem }): void; +}; + +export function createTagPlugin({ + getTagLabel, + onTag, + sourceIds, +}: CreateTagPluginProps): AutocompletePlugin { + const tagContainer = document.createElement('div'); + const tags = []; + + return { + subscribe({ onSelect, refresh, setQuery }) { + window.requestAnimationFrame(() => { + const inputWrapperPrefix = document.querySelector( + '.aa-InputWrapperPrefix' + ); + inputWrapperPrefix.appendChild(tagContainer); + + const input = document.querySelector('.aa-Input'); + + input?.addEventListener('keydown', (event) => { + if ( + tags.length > 0 && + input.selectionStart === 0 && + event.key === 'Backspace' + ) { + tags.splice(tags[tags.length - 1], 1); + refresh(); + } + }); + }); + + onSelect((params) => { + if (!sourceIds.includes(params.source.sourceId)) { + return; + } + + tags.push(getTagLabel(params)); + setQuery(''); + refresh(); + onTag(params); + }); + }, + onStateChange({ refresh }) { + render( +
      + {tags.map((tag, index) => ( +
    • + { + tags.splice(tags.indexOf(tag), 1); + refresh(); + document + .querySelector('.aa-Input') + ?.focus(); + }} + /> +
    • + ))} +
    , + tagContainer + ); + }, + }; +} + +type AutocompleteTagProps = { + label: any; + onRemove(): void; +}; + +function AutocompleteTag({ label, onRemove }: AutocompleteTagProps) { + return ( + + ); +} From 2fc30e16959e3439a2c77647a800dd6271ff6707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Thu, 21 Jan 2021 14:47:30 +0100 Subject: [PATCH 16/22] feat(js): change renderer implementation to virtual DOM (#381) * feat(js): change renderer implementation to virtual DOM * feat(highlighting): revamp highlighting system to VDOM (#399) --- examples/js/app.tsx | 66 ++----- examples/js/createTagPlugin.tsx | 103 ---------- examples/js/index.html | 3 + .../src/__tests__/autocomplete.test.ts | 1 - packages/autocomplete-js/src/autocomplete.ts | 2 +- packages/autocomplete-js/src/render.ts | 182 ++++++++++++++++++ .../src/getTemplates.ts | 82 ++++++++ .../src/getTemplates.ts | 83 ++++++++ test/utils/index.ts | 1 - test/utils/wait.ts | 5 - 10 files changed, 363 insertions(+), 165 deletions(-) delete mode 100644 examples/js/createTagPlugin.tsx create mode 100644 packages/autocomplete-js/src/render.ts create mode 100644 packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts create mode 100644 packages/autocomplete-plugin-recent-searches/src/getTemplates.ts delete mode 100644 test/utils/wait.ts diff --git a/examples/js/app.tsx b/examples/js/app.tsx index 0faeda914..dcb94afbf 100644 --- a/examples/js/app.tsx +++ b/examples/js/app.tsx @@ -2,25 +2,19 @@ import { autocomplete, getAlgoliaHits, - snippetHit, + highlightHit, } from '@algolia/autocomplete-js'; import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; import { Hit } from '@algolia/client-search'; import algoliasearch from 'algoliasearch'; -import { h, Fragment } from 'preact'; +import { h } from 'preact'; import insightsClient from 'search-insights'; import '@algolia/autocomplete-theme-classic'; -import { shortcutsPlugin } from './shortcutsPlugin'; - -type Product = { - name: string; - image: string; - description: string; -}; +type Product = { name: string; image: string }; type ProductHit = Hit; const appId = 'latency'; @@ -50,7 +44,6 @@ autocomplete({ debug: true, openOnFocus: true, plugins: [ - shortcutsPlugin, algoliaInsightsPlugin, recentSearchesPlugin, querySuggestionsPlugin, @@ -65,38 +58,16 @@ autocomplete({ getItems() { return getAlgoliaHits({ searchClient, - queries: [ - { - indexName: 'instant_search', - query, - params: { - clickAnalytics: true, - attributesToSnippet: ['name:10', 'description:35'], - snippetEllipsisText: '…', - }, - }, - ], + queries: [{ indexName: 'instant_search', query }], }); }, templates: { - header() { - return ( - - Products -
    -
    - ); - }, item({ item }) { return ; }, empty() { return ( -
    -
    - No products for this query. -
    -
    +
    No results for this query.
    ); }, }, @@ -111,27 +82,14 @@ type ProductItemProps = { function ProductItem({ hit }: ProductItemProps) { return ( - -
    - {hit.name} +
    +
    + {hit.name}
    -
    -
    - {snippetHit({ hit, attribute: 'name' })} -
    -
    - {snippetHit({ hit, attribute: 'description' })} -
    + +
    + {highlightHit({ hit, attribute: 'name' })}
    - - +
    ); } diff --git a/examples/js/createTagPlugin.tsx b/examples/js/createTagPlugin.tsx deleted file mode 100644 index f29c8f541..000000000 --- a/examples/js/createTagPlugin.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/** @jsx h */ -import { AutocompletePlugin } from '@algolia/autocomplete-js'; -import { h, render } from 'preact'; - -type CreateTagPluginProps = { - sourceIds?: string[]; - getTagLabel({ item }: { item: TItem }): any; - onTag({ item }: { item: TItem }): void; -}; - -export function createTagPlugin({ - getTagLabel, - onTag, - sourceIds, -}: CreateTagPluginProps): AutocompletePlugin { - const tagContainer = document.createElement('div'); - const tags = []; - - return { - subscribe({ onSelect, refresh, setQuery }) { - window.requestAnimationFrame(() => { - const inputWrapperPrefix = document.querySelector( - '.aa-InputWrapperPrefix' - ); - inputWrapperPrefix.appendChild(tagContainer); - - const input = document.querySelector('.aa-Input'); - - input?.addEventListener('keydown', (event) => { - if ( - tags.length > 0 && - input.selectionStart === 0 && - event.key === 'Backspace' - ) { - tags.splice(tags[tags.length - 1], 1); - refresh(); - } - }); - }); - - onSelect((params) => { - if (!sourceIds.includes(params.source.sourceId)) { - return; - } - - tags.push(getTagLabel(params)); - setQuery(''); - refresh(); - onTag(params); - }); - }, - onStateChange({ refresh }) { - render( -
      - {tags.map((tag, index) => ( -
    • - { - tags.splice(tags.indexOf(tag), 1); - refresh(); - document - .querySelector('.aa-Input') - ?.focus(); - }} - /> -
    • - ))} -
    , - tagContainer - ); - }, - }; -} - -type AutocompleteTagProps = { - label: any; - onRemove(): void; -}; - -function AutocompleteTag({ label, onRemove }: AutocompleteTagProps) { - return ( - - ); -} diff --git a/examples/js/index.html b/examples/js/index.html index ab029cd48..de1e2fef3 100644 --- a/examples/js/index.html +++ b/examples/js/index.html @@ -48,7 +48,10 @@
    +<<<<<<< HEAD +======= +>>>>>>> 7be2249c (feat(js): change renderer implementation to virtual DOM (#381)) diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 240359ddb..65100ad22 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -1,6 +1,5 @@ import { fireEvent, waitFor } from '@testing-library/dom'; -import { wait } from '../../../../test/utils'; import { autocomplete } from '../autocomplete'; describe('autocomplete-js', () => { diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index bcc026da5..5f58540f3 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -155,7 +155,7 @@ export function autocomplete( ); const render = - (!getItemsCount(renderProps.state) && + (!getItemsCount(state) && !hasEmptySourceTemplateRef.current && props.value.renderer.renderEmpty) || props.value.renderer.render; diff --git a/packages/autocomplete-js/src/render.ts b/packages/autocomplete-js/src/render.ts new file mode 100644 index 000000000..d3cbd02ca --- /dev/null +++ b/packages/autocomplete-js/src/render.ts @@ -0,0 +1,182 @@ +import { + AutocompleteApi as AutocompleteCoreApi, + AutocompleteScopeApi, +} from '@algolia/autocomplete-core'; +import { BaseItem } from '@algolia/autocomplete-core/src'; + +import { + AutocompleteClassNames, + AutocompleteDom, + AutocompletePropGetters, + AutocompleteRender, + AutocompleteState, + Pragma, + PragmaFrag, +} from './types'; +import { setProperties, setPropertiesWithoutEvents } from './utils'; + +type RenderProps = { + autocomplete: AutocompleteCoreApi; + autocompleteScopeApi: AutocompleteScopeApi; + classNames: AutocompleteClassNames; + createElement: Pragma; + dom: AutocompleteDom; + Fragment: PragmaFrag; + isTouch: boolean; + panelContainer: HTMLElement; + propGetters: AutocompletePropGetters; + state: AutocompleteState; +}; + +export function renderSearchBox({ + autocomplete, + autocompleteScopeApi, + dom, + propGetters, + state, +}: RenderProps): void { + setPropertiesWithoutEvents( + dom.root, + propGetters.getRootProps({ + state, + props: autocomplete.getRootProps({}), + ...autocompleteScopeApi, + }) + ); + setPropertiesWithoutEvents( + dom.input, + propGetters.getInputProps({ + state, + props: autocomplete.getInputProps({ inputElement: dom.input }), + inputElement: dom.input, + ...autocompleteScopeApi, + }) + ); + setProperties(dom.label, { hidden: state.status === 'stalled' }); + setProperties(dom.loadingIndicator, { hidden: state.status !== 'stalled' }); + setProperties(dom.resetButton, { hidden: !state.query }); +} + +export function renderPanel( + render: AutocompleteRender, + { + autocomplete, + autocompleteScopeApi, + classNames, + createElement, + dom, + Fragment, + isTouch, + panelContainer, + propGetters, + state, + }: RenderProps +): void { + if (!state.isOpen) { + if (panelContainer.contains(dom.panel)) { + panelContainer.removeChild(dom.panel); + } + + return; + } + + // We add the panel element to the DOM when it's not yet appended and that the + // items are fetched. + if (!panelContainer.contains(dom.panel) && state.status !== 'loading') { + panelContainer.appendChild(dom.panel); + } + + dom.panel.classList.toggle('aa-Panel--desktop', !isTouch); + dom.panel.classList.toggle('aa-Panel--touch', isTouch); + dom.panel.classList.toggle('aa-Panel--stalled', state.status === 'stalled'); + + const sections = state.collections.map(({ source, items }, sourceIndex) => { + return createElement('section', { + key: sourceIndex, + className: classNames.source, + children: createElement('ul', { + className: classNames.list, + ...propGetters.getListProps({ + state, + props: autocomplete.getListProps({}), + ...autocompleteScopeApi, + }), + children: [ + source.templates.header && + createElement('div', { + className: classNames.sourceHeader, + children: [ + source.templates.header({ + createElement, + Fragment, + items, + source, + state, + }), + ], + }), + items.length === 0 && source.templates.empty + ? createElement('div', { + className: classNames.sourceEmpty, + children: [ + source.templates.empty({ + createElement, + Fragment, + source, + state, + }), + ], + }) + : createElement('ul', { + className: classNames.list, + children: [ + ...items.map((item) => { + const itemProps = autocomplete.getItemProps({ + item, + source, + }); + + return createElement('li', { + key: itemProps.id, + className: classNames.item, + ...propGetters.getItemProps({ + state, + props: itemProps, + ...autocompleteScopeApi, + }), + children: [ + source.templates.item({ + createElement, + Fragment, + item, + state, + }), + ], + }); + }), + ], + }), + source.templates.footer && + createElement('div', { + className: classNames.sourceFooter, + children: [ + source.templates.footer({ + createElement, + Fragment, + items, + source, + state, + }), + ], + }), + ], + }), + }); + }); + const children = createElement('div', { + className: 'aa-PanelLayout', + children: sections, + }); + + render({ children, state, sections, createElement, Fragment }, dom.panel); +} diff --git a/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts b/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts new file mode 100644 index 000000000..74ba8280a --- /dev/null +++ b/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts @@ -0,0 +1,82 @@ +import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; + +import { QuerySuggestionsHit } from './types'; + +export type GetTemplatesParams = { + onTapAhead(item: TItem): void; +}; + +export function getTemplates({ + onTapAhead, +}: GetTemplatesParams): SourceTemplates { + return { + item({ item, createElement, Fragment }) { + return createElement(Fragment, { + children: [ + createElement('div', { + className: 'aa-ItemContent', + children: [ + createElement('div', { + className: 'aa-ItemSourceIcon', + children: [ + createElement( + 'svg', + { + width: '20', + height: '20', + viewBox: '0 0 20 20', + }, + createElement('path', { + d: + 'M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z', + stroke: 'currentColor', + fill: 'none', + 'fill-rule': 'evenodd', + 'stroke-width': '1.4', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + }) + ), + ], + }), + createElement('div', { + className: 'aa-ItemTitle', + children: reverseHighlightHit({ + hit: item, + attribute: 'query', + }), + }), + ], + }), + createElement('button', { + className: 'aa-ItemActionButton', + title: `Fill query with "${item.query}"`, + onClick(event: MouseEvent) { + event.stopPropagation(); + onTapAhead(item); + }, + children: [ + createElement( + 'svg', + { + viewBox: '0 0 24 24', + fill: 'currentColor', + width: '18', + height: '18', + }, + createElement('rect', { + fill: 'none', + height: '24', + width: '24', + }), + createElement('path', { + d: 'M5,15h2V8.41L18.59,20L20,18.59L8.41,7H15V5H5V15z', + }) + ), + ], + }), + ], + }); + }, + }; +} diff --git a/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts b/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts new file mode 100644 index 000000000..b30c696df --- /dev/null +++ b/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts @@ -0,0 +1,83 @@ +import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; + +import { RecentSearchesItem } from './types'; + +export type GetTemplatesParams = { + onRemove(id: string): void; +}; + +export function getTemplates({ + onRemove, +}: GetTemplatesParams): SourceTemplates { + return { + item({ item, createElement, Fragment }) { + return createElement(Fragment, { + children: [ + createElement('div', { + className: 'aa-ItemContent', + children: [ + createElement('div', { + className: 'aa-ItemSourceIcon', + children: [ + createElement( + 'svg', + { + width: '20', + height: '20', + viewBox: '0 0 22 22', + fill: 'currentColor', + }, + createElement('path', { + d: 'M0 0h24v24H0z', + fill: 'none', + }), + createElement('path', { + d: + 'M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z', + }) + ), + ], + }), + createElement('div', { + className: 'aa-ItemTitle', + children: reverseHighlightHit({ + hit: item, + attribute: 'query', + }), + }), + ], + }), + createElement('button', { + className: 'aa-ItemActionButton', + title: 'Remove', + onClick(event: MouseEvent) { + event.stopPropagation(); + onRemove(item.id); + }, + children: [ + createElement( + 'svg', + { + width: '20', + height: '20', + viewBox: '0 0 20 20', + fill: 'currentColor', + }, + createElement('path', { + d: + 'M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z', + stroke: 'currentColor', + fill: 'none', + 'fill-rule': 'evenodd', + 'stroke-width': '1.4', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + }) + ), + ], + }), + ], + }); + }, + }; +} diff --git a/test/utils/index.ts b/test/utils/index.ts index 551720c4d..912a9c3f6 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -8,4 +8,3 @@ export * from './createSource'; export * from './createState'; export * from './defer'; export * from './runAllMicroTasks'; -export * from './wait'; diff --git a/test/utils/wait.ts b/test/utils/wait.ts deleted file mode 100644 index a68b7baa2..000000000 --- a/test/utils/wait.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function wait(time: number) { - return new Promise((resolve) => { - setTimeout(resolve, time); - }); -} From 0563493e65e62b00507784319fd28385834f48ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 29 Jan 2021 17:17:31 +0100 Subject: [PATCH 17/22] docs(website): update Playground link --- packages/website/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/docusaurus.config.js b/packages/website/docusaurus.config.js index 8f3c92d12..77861668d 100644 --- a/packages/website/docusaurus.config.js +++ b/packages/website/docusaurus.config.js @@ -28,7 +28,7 @@ module.exports = { { label: 'Playground', to: - 'https://codesandbox.io/s/github/algolia/autocomplete.js/tree/next/examples/js?file=/app.ts', + 'https://codesandbox.io/s/github/algolia/autocomplete.js/tree/next/examples/js?file=/app.tsx', position: 'right', }, { From d869f075c26862672a96b07093587424216cfe23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 1 Feb 2021 20:49:01 +0100 Subject: [PATCH 18/22] feat(website): add `AutocompleteExample` component In a docs page (either `.md` or `.mdx`), import the component, add it and give it the props you need: ```mdx import { AutocompleteExample } from '@site/src/components/AutocompleteExample'; Content... { return [ { getItems() { return [{ label: 'Twitter' }, { label: 'GitHub' }]; }, templates: { item({ item }) { return item.label; }, }, }, ]; }} /> Content... ``` --- .../src/components/AutocompleteExample.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/website/src/components/AutocompleteExample.tsx diff --git a/packages/website/src/components/AutocompleteExample.tsx b/packages/website/src/components/AutocompleteExample.tsx new file mode 100644 index 000000000..71db18cd3 --- /dev/null +++ b/packages/website/src/components/AutocompleteExample.tsx @@ -0,0 +1,23 @@ +import { autocomplete, AutocompleteOptions } from '@algolia/autocomplete-js'; +import React, { useEffect, useRef } from 'react'; + +import '@algolia/autocomplete-theme-classic'; + +export function AutocompleteExample( + props: Omit, 'container'> +) { + const containerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) { + return; + } + + autocomplete({ + container: containerRef.current, + ...props, + }); + }, [props]); + + return
    ; +} From e36ea327554f680639cf3f3f4c6a137e6527fbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Wed, 3 Feb 2021 09:54:16 +0100 Subject: [PATCH 19/22] chore: fix merge --- .eslintrc.js | 6 + examples/js/index.html | 3 - packages/autocomplete-js/src/autocomplete.ts | 5 - packages/autocomplete-js/src/render.ts | 182 ------------------ .../src/getTemplates.ts | 82 -------- .../src/getTemplates.ts | 83 -------- 6 files changed, 6 insertions(+), 355 deletions(-) delete mode 100644 packages/autocomplete-js/src/render.ts delete mode 100644 packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts delete mode 100644 packages/autocomplete-plugin-recent-searches/src/getTemplates.ts diff --git a/.eslintrc.js b/.eslintrc.js index 968d2eba7..3e2cdb389 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -69,6 +69,12 @@ module.exports = { 'eslint-comments/no-unlimited-disable': 0, }, }, + { + files: ['packages/website/**/*'], + rules: { + 'import/no-extraneous-dependencies': 0, + }, + }, { files: ['**/rollup.config.js', 'stories/**/*', '**/__tests__/**'], rules: { diff --git a/examples/js/index.html b/examples/js/index.html index de1e2fef3..ab029cd48 100644 --- a/examples/js/index.html +++ b/examples/js/index.html @@ -48,10 +48,7 @@
    -<<<<<<< HEAD -======= ->>>>>>> 7be2249c (feat(js): change renderer implementation to virtual DOM (#381)) diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 5f58540f3..08fd0e03f 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -144,11 +144,6 @@ export function autocomplete( propGetters, state: lastStateRef.current, }; - const render = - (!getItemsCount(state) && - !hasEmptySourceTemplateRef.current && - props.value.renderer.renderEmpty) || - props.value.renderer.render; hasEmptySourceTemplateRef.current = renderProps.state.collections.some( (collection) => collection.source.templates.empty diff --git a/packages/autocomplete-js/src/render.ts b/packages/autocomplete-js/src/render.ts deleted file mode 100644 index d3cbd02ca..000000000 --- a/packages/autocomplete-js/src/render.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { - AutocompleteApi as AutocompleteCoreApi, - AutocompleteScopeApi, -} from '@algolia/autocomplete-core'; -import { BaseItem } from '@algolia/autocomplete-core/src'; - -import { - AutocompleteClassNames, - AutocompleteDom, - AutocompletePropGetters, - AutocompleteRender, - AutocompleteState, - Pragma, - PragmaFrag, -} from './types'; -import { setProperties, setPropertiesWithoutEvents } from './utils'; - -type RenderProps = { - autocomplete: AutocompleteCoreApi; - autocompleteScopeApi: AutocompleteScopeApi; - classNames: AutocompleteClassNames; - createElement: Pragma; - dom: AutocompleteDom; - Fragment: PragmaFrag; - isTouch: boolean; - panelContainer: HTMLElement; - propGetters: AutocompletePropGetters; - state: AutocompleteState; -}; - -export function renderSearchBox({ - autocomplete, - autocompleteScopeApi, - dom, - propGetters, - state, -}: RenderProps): void { - setPropertiesWithoutEvents( - dom.root, - propGetters.getRootProps({ - state, - props: autocomplete.getRootProps({}), - ...autocompleteScopeApi, - }) - ); - setPropertiesWithoutEvents( - dom.input, - propGetters.getInputProps({ - state, - props: autocomplete.getInputProps({ inputElement: dom.input }), - inputElement: dom.input, - ...autocompleteScopeApi, - }) - ); - setProperties(dom.label, { hidden: state.status === 'stalled' }); - setProperties(dom.loadingIndicator, { hidden: state.status !== 'stalled' }); - setProperties(dom.resetButton, { hidden: !state.query }); -} - -export function renderPanel( - render: AutocompleteRender, - { - autocomplete, - autocompleteScopeApi, - classNames, - createElement, - dom, - Fragment, - isTouch, - panelContainer, - propGetters, - state, - }: RenderProps -): void { - if (!state.isOpen) { - if (panelContainer.contains(dom.panel)) { - panelContainer.removeChild(dom.panel); - } - - return; - } - - // We add the panel element to the DOM when it's not yet appended and that the - // items are fetched. - if (!panelContainer.contains(dom.panel) && state.status !== 'loading') { - panelContainer.appendChild(dom.panel); - } - - dom.panel.classList.toggle('aa-Panel--desktop', !isTouch); - dom.panel.classList.toggle('aa-Panel--touch', isTouch); - dom.panel.classList.toggle('aa-Panel--stalled', state.status === 'stalled'); - - const sections = state.collections.map(({ source, items }, sourceIndex) => { - return createElement('section', { - key: sourceIndex, - className: classNames.source, - children: createElement('ul', { - className: classNames.list, - ...propGetters.getListProps({ - state, - props: autocomplete.getListProps({}), - ...autocompleteScopeApi, - }), - children: [ - source.templates.header && - createElement('div', { - className: classNames.sourceHeader, - children: [ - source.templates.header({ - createElement, - Fragment, - items, - source, - state, - }), - ], - }), - items.length === 0 && source.templates.empty - ? createElement('div', { - className: classNames.sourceEmpty, - children: [ - source.templates.empty({ - createElement, - Fragment, - source, - state, - }), - ], - }) - : createElement('ul', { - className: classNames.list, - children: [ - ...items.map((item) => { - const itemProps = autocomplete.getItemProps({ - item, - source, - }); - - return createElement('li', { - key: itemProps.id, - className: classNames.item, - ...propGetters.getItemProps({ - state, - props: itemProps, - ...autocompleteScopeApi, - }), - children: [ - source.templates.item({ - createElement, - Fragment, - item, - state, - }), - ], - }); - }), - ], - }), - source.templates.footer && - createElement('div', { - className: classNames.sourceFooter, - children: [ - source.templates.footer({ - createElement, - Fragment, - items, - source, - state, - }), - ], - }), - ], - }), - }); - }); - const children = createElement('div', { - className: 'aa-PanelLayout', - children: sections, - }); - - render({ children, state, sections, createElement, Fragment }, dom.panel); -} diff --git a/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts b/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts deleted file mode 100644 index 74ba8280a..000000000 --- a/packages/autocomplete-plugin-query-suggestions/src/getTemplates.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; - -import { QuerySuggestionsHit } from './types'; - -export type GetTemplatesParams = { - onTapAhead(item: TItem): void; -}; - -export function getTemplates({ - onTapAhead, -}: GetTemplatesParams): SourceTemplates { - return { - item({ item, createElement, Fragment }) { - return createElement(Fragment, { - children: [ - createElement('div', { - className: 'aa-ItemContent', - children: [ - createElement('div', { - className: 'aa-ItemSourceIcon', - children: [ - createElement( - 'svg', - { - width: '20', - height: '20', - viewBox: '0 0 20 20', - }, - createElement('path', { - d: - 'M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z', - stroke: 'currentColor', - fill: 'none', - 'fill-rule': 'evenodd', - 'stroke-width': '1.4', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round', - }) - ), - ], - }), - createElement('div', { - className: 'aa-ItemTitle', - children: reverseHighlightHit({ - hit: item, - attribute: 'query', - }), - }), - ], - }), - createElement('button', { - className: 'aa-ItemActionButton', - title: `Fill query with "${item.query}"`, - onClick(event: MouseEvent) { - event.stopPropagation(); - onTapAhead(item); - }, - children: [ - createElement( - 'svg', - { - viewBox: '0 0 24 24', - fill: 'currentColor', - width: '18', - height: '18', - }, - createElement('rect', { - fill: 'none', - height: '24', - width: '24', - }), - createElement('path', { - d: 'M5,15h2V8.41L18.59,20L20,18.59L8.41,7H15V5H5V15z', - }) - ), - ], - }), - ], - }); - }, - }; -} diff --git a/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts b/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts deleted file mode 100644 index b30c696df..000000000 --- a/packages/autocomplete-plugin-recent-searches/src/getTemplates.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { reverseHighlightHit, SourceTemplates } from '@algolia/autocomplete-js'; - -import { RecentSearchesItem } from './types'; - -export type GetTemplatesParams = { - onRemove(id: string): void; -}; - -export function getTemplates({ - onRemove, -}: GetTemplatesParams): SourceTemplates { - return { - item({ item, createElement, Fragment }) { - return createElement(Fragment, { - children: [ - createElement('div', { - className: 'aa-ItemContent', - children: [ - createElement('div', { - className: 'aa-ItemSourceIcon', - children: [ - createElement( - 'svg', - { - width: '20', - height: '20', - viewBox: '0 0 22 22', - fill: 'currentColor', - }, - createElement('path', { - d: 'M0 0h24v24H0z', - fill: 'none', - }), - createElement('path', { - d: - 'M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z', - }) - ), - ], - }), - createElement('div', { - className: 'aa-ItemTitle', - children: reverseHighlightHit({ - hit: item, - attribute: 'query', - }), - }), - ], - }), - createElement('button', { - className: 'aa-ItemActionButton', - title: 'Remove', - onClick(event: MouseEvent) { - event.stopPropagation(); - onRemove(item.id); - }, - children: [ - createElement( - 'svg', - { - width: '20', - height: '20', - viewBox: '0 0 20 20', - fill: 'currentColor', - }, - createElement('path', { - d: - 'M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z', - stroke: 'currentColor', - fill: 'none', - 'fill-rule': 'evenodd', - 'stroke-width': '1.4', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round', - }) - ), - ], - }), - ], - }); - }, - }; -} From 48fc7da58a83925efa6596df989f8018f3896137 Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Thu, 4 Feb 2021 11:41:16 -0700 Subject: [PATCH 20/22] docs: write "what is autocomplete"? (#393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(website): bootstrap new doc structure * docs(website): add doc for `createAlgoliaInsightsPlugin` * docs(website): add draft sections * docs: rewrite "Integrating keyboard navigation" (#389) * feat: add intro * Apply suggestions from code review Co-authored-by: Sarah Dayan * fix: remove conflicting sentence * docs: rewrite "Accessing data with Context" (#390) * docs: rewrote "Controlling behavior with State" (#391) * fix: bold and underline text * docs: rewrite "Populating autocomplete with Sources" (#396) * docs: write "Displaying items with Templates" (#397) * fix(core): update query conditions * chore(test): add tests for getEnvironmentProps onTouchMove (#398) * feat(emptyStates): implements empty source template and renderEmpty method (#395) * Implements `empty` template and `renderEmpty` method * Add wait function to `test/utils` folder Co-authored-by: François Chalifour * Add tests for `onActive` and `onSelect` on plugins (#400) * feat(js): change renderer implementation to virtual DOM (#381) * feat(js): change renderer implementation to virtual DOM * feat(highlighting): revamp highlighting system to VDOM (#399) * fix: fix code example (#403) * docs: switch core usage with autocomplete-js (#401) * docs: write "Basic configuration options" (#402) * docs: write "help and discussion" page (#394) * docs: add help page * Apply suggestions from code review Co-authored-by: François Chalifour Co-authored-by: François Chalifour * feat: put in a placeholder input * fix: update links * fix: links * fix: merge conflict * fix: merge conflict * feat: add AutocompleteExample * feat: add AutocompleteItem * fix: remove htm package * fix: remove htm package * fix: rename AutocompleteItem -> AutocompleteDocSearchItem * fix(website): use React renderer in `AutocompleteExample` * fix(website): support content record types Co-authored-by: François Chalifour Co-authored-by: Sarah Dayan Co-authored-by: Yannick Croissant Co-authored-by: Clément Vannicatte <20689156+shortcuts@users.noreply.github.com> Co-authored-by: François Chalifour --- packages/website/docs/introduction.md | 84 ++++++++++++++++++- .../components/AutocompleteDocSearchItem.tsx | 45 ++++++++++ .../src/components/AutocompleteExample.tsx | 7 +- 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 packages/website/src/components/AutocompleteDocSearchItem.tsx diff --git a/packages/website/docs/introduction.md b/packages/website/docs/introduction.md index df8f5609c..3237a264e 100644 --- a/packages/website/docs/introduction.md +++ b/packages/website/docs/introduction.md @@ -2,7 +2,87 @@ id: introduction title: What is Autocomplete? --- +import { AutocompleteExample } from '@site/src/components/AutocompleteExample'; +import { AutocompleteDocSearchItem } from '@site/src/components/AutocompleteDocSearchItem'; +import { getAlgoliaHits } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; +const searchClient = algoliasearch( + 'BH4D9OD16A', + 'a5c3ccfd361b8bcb9708e679c43ae0e5' +); -import Draft from './partials/draft.md' +Autocomplete is an open-source, production-ready JavaScript library for building autocomplete experiences. - +A user types into an input, and the autocomplete "completes" their thought by providing full terms or results: this is the very base of an autocomplete experience. + +For example, try typing the letter "s" in the search box below. + + { + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: 'autocomplete', + query, + params: { + hitsPerPage: 5 + } + } + ] + }); + }, + getItemUrl({ item }) { + return item.url; + }, + templates: { + item({ item }) { + return ( + + ); + } + }, + }, + ]; + }} +/> + +You can directly navigate to documentation on concepts like "sources" and "state". If you continue typing, say "st", the results update. You see pages having to do with "state," "getting started," and "static sources." + +**Autocomplete is now a ubiquitous part of most search experiences.** Search providers like Google, e-commerce sites like Amazon, and messaging apps like Slack all offer autocomplete experiences on mobile and desktop. + +While the search experience on this site displays links to pages, autocomplete experiences frequently show completed search terms. If you start a search by typing the letter "j" into Google, it suggests search terms like "javascript", "jest", "jsonlines", etc. Selecting one of these lands you on the search results page for the term. + +This experience minimizes typing, which is particularly impactful on mobile. It enables users to find what they're looking for quicker. It also exposes them to searches, products, or pages they may not have thought of but are interested in anyway. + +You may have already used Autocomplete-powered UIs. The [Algolia](https://www.algolia.com/doc/), [React Native](https://reactnative.dev/), [Tailwind CSS](https://tailwindcss.com/docs), and other documentation websites use Autocomplete via the [Algolia DocSearch](https://docsearch.algolia.com/) project. This library is flexible enough to power more than just documentation search though. **It's designed to help you build interactive and accessible autocomplete experiences, regardless of your use case.** + +## What this library provides + +Autocomplete is a JavaScript library that lets you quickly build autocomplete experiences. All you need to get started is: +- A container to inject the experience into +- Data to fill the autocomplete with + +The data that populates the autocomplete results are called [sources](sources). You can use whatever you want in your sources: a static set of searches terms, search results from an external source like an [Algolia](https://www.algolia.com/doc/guides/getting-started/what-is-algolia/) index, recent searches, and more. + +By configuring just those two required parameters ([`container`](autocomplete-js/#container) and [`getSources`](autocomplete-js/#getsources)) you can have an interactive autocomplete experience. **The library creates an input and provides the interactivity and accessibility attributes, but you're in full control of the DOM elements to output.** + +You don't have to display just suggested search terms, you can display links for actual results themselves (rather than links to results pages) or even display "actions" that a user can take from within an autocomplete. For example, you could let your users turn dark mode on, directly from an autocomplete, if they begin to type "darkmode". + +You can also display different data types (such as suggested search terms, product results, and actions) differently. The format of each data type and layout is customizable. + +## What this library doesn't provide + +Unlike [Algolia InstantSearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/), Autocomplete doesn't provide a library of ready-made UI widgets. You're in control of the full rendering of your autocomplete experience, and the library provides everything you need to make it functional and accessible. + +You're also in charge of providing the collection of items to display. You can easily plug Algolia results using [`getAlgoliaHits`](getAlgoliaHits-js) if you want, but you're free to use Autocomplete with any data sources you want. + +Ready to learn more? Move on to [Getting Started](getting-started) to see a basic example in action. diff --git a/packages/website/src/components/AutocompleteDocSearchItem.tsx b/packages/website/src/components/AutocompleteDocSearchItem.tsx new file mode 100644 index 000000000..f46650a12 --- /dev/null +++ b/packages/website/src/components/AutocompleteDocSearchItem.tsx @@ -0,0 +1,45 @@ +import { Hit } from '@algolia/client-search'; +import React from 'react'; + +type DocSearchItem = { + content: string | null; + url: string; + url_without_anchor: string; + type: + | 'content' + | 'lvl0' + | 'lvl1' + | 'lvl2' + | 'lvl3' + | 'lvl4' + | 'lvl5' + | 'lvl6'; + anchor: string | null; + hierarchy: { + lvl0: string; + lvl1: string; + lvl2: string | null; + lvl3: string | null; + lvl4: string | null; + lvl5: string | null; + lvl6: string | null; + }; +}; +type DocSearchHit = Hit; +type AutocompleteItemProps = { hit: DocSearchHit; breadcrumb: string[] }; + +export function AutocompleteDocSearchItem({ + hit, + breadcrumb, +}: AutocompleteItemProps) { + const title = hit.type === 'content' ? hit.content : hit.hierarchy[hit.type]; + + return ( + +
    +
    {title}
    +
    {breadcrumb.join(' • ')}
    +
    +
    + ); +} diff --git a/packages/website/src/components/AutocompleteExample.tsx b/packages/website/src/components/AutocompleteExample.tsx index 71db18cd3..e0accec8f 100644 --- a/packages/website/src/components/AutocompleteExample.tsx +++ b/packages/website/src/components/AutocompleteExample.tsx @@ -1,5 +1,6 @@ import { autocomplete, AutocompleteOptions } from '@algolia/autocomplete-js'; -import React, { useEffect, useRef } from 'react'; +import React, { createElement, Fragment, useEffect, useRef } from 'react'; +import { render } from 'react-dom'; import '@algolia/autocomplete-theme-classic'; @@ -15,6 +16,10 @@ export function AutocompleteExample( autocomplete({ container: containerRef.current, + renderer: { createElement, Fragment }, + render({ children }, root) { + render(children as any, root); + }, ...props, }); }, [props]); From e75218b6bb07d09d52f13cb483a84be222f3c301 Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Thu, 4 Feb 2021 13:05:20 -0700 Subject: [PATCH 21/22] docs: write "getting started" (#415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(website): bootstrap new doc structure * docs(website): add doc for `createAlgoliaInsightsPlugin` * docs(website): add draft sections * docs: rewrite "Integrating keyboard navigation" (#389) * docs: rewrite "Accessing data with Context" (#390) * docs: rewrote "Controlling behavior with State" (#391) * docs: rewrite "Populating autocomplete with Sources" (#396) * docs: write "Displaying items with Templates" (#397) * fix(core): update query conditions * chore(test): add tests for getEnvironmentProps onTouchMove (#398) * feat(emptyStates): implements empty source template and renderEmpty method (#395) * Implements `empty` template and `renderEmpty` method * Add wait function to `test/utils` folder Co-authored-by: François Chalifour * Add tests for `onActive` and `onSelect` on plugins (#400) * feat(js): change renderer implementation to virtual DOM (#381) * feat(js): change renderer implementation to virtual DOM * feat(highlighting): revamp highlighting system to VDOM (#399) * fix: fix code example (#403) * docs: switch core usage with autocomplete-js (#401) * docs: write "Basic configuration options" (#402) * docs: write "help and discussion" page (#394) * docs: add help page * Apply suggestions from code review Co-authored-by: François Chalifour Co-authored-by: François Chalifour * feat: getting started first draft * Apply suggestions from code review Co-authored-by: François Chalifour * feat: update code example * fix: copy-edit * feat: add placeholders * fix: remove unnecessary code snippets * fix: links * fix: add record example * fix: rename titles to be more user focused * fix: correct URL * fix: update links * Apply suggestions from code review Co-authored-by: Sarah Dayan * fix: update installation headers and add note * fix: remove headless installation and add 3rd party caveat * Update packages/website/docs/getting-started.md Co-authored-by: François Chalifour * fix: remove CDNJS * Update packages/website/docs/getting-started.md Co-authored-by: François Chalifour * Update packages/website/docs/getting-started.md * Update packages/website/docs/getting-started.md Co-authored-by: Sarah Dayan * feat: add AutocompleteExample * fix: merge docs-1 Co-authored-by: François Chalifour Co-authored-by: Sarah Dayan Co-authored-by: Yannick Croissant Co-authored-by: Clément Vannicatte <20689156+shortcuts@users.noreply.github.com> Co-authored-by: François Chalifour --- packages/website/docs/getting-started.md | 348 ++++++++++++++++++++--- 1 file changed, 301 insertions(+), 47 deletions(-) diff --git a/packages/website/docs/getting-started.md b/packages/website/docs/getting-started.md index 3662df3d7..2fb1c1be7 100644 --- a/packages/website/docs/getting-started.md +++ b/packages/website/docs/getting-started.md @@ -2,45 +2,32 @@ id: getting-started title: Getting Started --- +import { AutocompleteExample } from '@site/src/components/AutocompleteExample'; +import { AutocompleteDocSearchItem } from '@site/src/components/AutocompleteDocSearchItem'; +import { getAlgoliaHits } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch/lite'; +const searchClient = algoliasearch( + 'BH4D9OD16A', + 'a5c3ccfd361b8bcb9708e679c43ae0e5' +); -:::note Draft +Get started with Autocomplete by building an Algolia search experience. -This page needs to cover: +This documentation offers a few ways to learn about the Autocomplete library: + - Read the [**Core Concepts**](basic-options) to learn more about underlying principles, like [**Sources**](sources) and [**State**](state). + - Follow the [**Guides**](using-query-suggestions-plugin) to understand how to build common UX patterns. + - Refer to [**API reference**](api) for a comprehensive list of parameters and options. + - Try out the [**Playground**](https://codesandbox.io/s/github/algolia/autocomplete.js/tree/next/examples/js?file=/app.ts) where you can fork a basic implementation and play around. -- These docs provide a few ways to learn how to use Autocomplete: - - Read about **Core Concepts**—here you can learn more about underlying principles, like **Sources** and **State**. - - Follow our **Guides** to understand how to build common UX patterns. - - Refer to **API reference**. - - [Maybe v2] Play in the **Playground** where you can select components of your autocomplete and we provide you with the code. -- Keep reading for a simple sample implementation -- Installation - - JS - - Core -- A basic example - - Commented code snippet on a basic example (only using query-suggestions plug-in) with resulting UI and links to various **Core Concepts.** - -::: - -This page is an overview of the Autocomplete documentation and related resources. - -Autocomplete is a JavaScript library for **building autocomplete search experiences**. - -## Features - -- Displays suggestions as you type -- Provides autocompletion -- Supports custom templates for UI flexibility -- Works well with RTL languages -- Triggers custom hooks to plug your logic -- Plugs easily to Algolia's realtime search engine - -## What is Autocomplete +Keep reading to see how to install Autocomplete and build a basic implementation with Algolia. ## Installation -Autocomplete is available on the [npm](https://www.npmjs.com/) registry. +The recommended way to get started is with the [`autocomplete-js`](autocomplete-js) package. It includes everything you need to render a JavaScript autocomplete experience. -### JavaScript +Otherwise, you can install the [`autocomplete-core`](createAutocomplete) package if you want to [build a renderer](creating-a-renderer) from scratch. + +All Autocomplete packages are available on the [npm](https://www.npmjs.com/) registry. ```bash yarn add @algolia/autocomplete-js@alpha @@ -48,30 +35,297 @@ yarn add @algolia/autocomplete-js@alpha npm install @algolia/autocomplete-js@alpha ``` -If you do not wish to use a package manager, you can use standalone endpoints: +If you don't want to use a package manager, you can use a standalone endpoint: ```html - +``` + +We recommend using jsDeliver but [`autocomplete-js`](autocomplete-js) is also available through [unpkg](https://unpkg.com/@algolia/autocomplete-js@alpha). + +:::note + +We don't provide support regarding third party services like jsDeliver or other CDNs. + +::: + +## Defining where to put your autocomplete - - +To get started, you need a container for your autocomplete to go in. If you don't have one already, you can insert one into your markup: + +```js title="HTML" +
    ``` -### Headless +Then, insert your autocomplete into it by calling the [`autocomplete`](autocomplete-js) function and providing the [`container`](autocomplete-js/#container). It can be a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) or an [Element](https://developer.mozilla.org/docs/Web/API/HTMLElement). -```bash -yarn add @algolia/autocomplete-core@alpha -# or -npm install @algolia/autocomplete-core@alpha +Make sure to provide a container (e.g., a `div`), not an `input`. Autocomplete generates a fully accessible search box for you. + +```js title="JavaScript" +import { autocomplete } from '@algolia/autocomplete-js'; + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search the autocomplete documentation', + openOnFocus: true, + getSources() { + return []; + }, +}); ``` -If you do not wish to use a package manager, you can use standalone endpoints: +You may have noticed three new options: [`placeholder`](autocomplete-js#placeholder), [`openOnFocus`](autocomplete-js#openonfocus), and [`getSources`](sources#getsources). -```html - - +The [`placeholder`](autocomplete-js#placeholder) option defines the placeholder text to show until the user starts typing in the input, while the [`openOnFocus`](autocomplete-js#openonfocus) option determines whether to open the panel on [focus](https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event) or not, even when there's no query. It defaults to `false`, so you need to set it to `true` if you want the dropdown to appear as soon as a user clicks on it. + +Autocomplete is now plugged in. But you won't see anything appear until you define your [sources](sources). + +## Defining what items to display + +[Sources](sources) define where to retrieve the items to display in your autocomplete dropdown. You define your sources in the [`getSources`](sources#getsources) function by returning an array of [source objects](sources#source). Each source object needs to include a [`getItems`](sources#getitems) function that returns the items to display. Sources can be static or dynamic. + +This example uses the [Algolia index](https://www.algolia.com/doc/faq/basics/what-is-an-index/) [powering the documentation search](https://docsearch.algolia.com/) on this site as a source. The [`autocomplete-js`](autocomplete-js) package provides a built-in [`getAlgoliaHits`](getAlgoliaHits) function for just this purpose. + +```js title="JavaScript" +import algoliasearch from 'algoliasearch/lite'; +import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; - - +const searchClient = algoliasearch( + 'BH4D9OD16A', + 'a5c3ccfd361b8bcb9708e679c43ae0e5' +); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search documentation', + openOnFocus: true, + getSources({ query }) { + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: 'autocomplete', + query, + params: { + hitsPerPage: 10 + } + } + ] + }); + }, + } + ]; + } +}); +``` + +The preset requires an [Algolia search client](https://www.algolia.com/doc/api-client/getting-started/install/javascript/) initialized with an [Algolia application ID and API key](https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-with-the-api/#application-id). It lets you search into your Algolia index using an array of `queries`, which defines one or more queries to send to the index. + +This example makes just one query to the "autocomplete" index using the `query` from [`getSources`](sources#getsources). It passes one additional parameter, [`hitsPerPage`](https://www.algolia.com/doc/api-reference/api-parameters/hitsPerPage/) to define how many items to display, but you could pass any other [Algolia query parameters](https://www.algolia.com/doc/api-reference/api-parameters/). + +Although you've now declared what items display using [`getSources`](sources#getsources), you still won't see anything until you've defined _how_ to display the items you've retrieved. + +## Defining how to display items + +[Sources](sources) also define how to display items in your Autocomplete using [`templates`](templates). Templates can return a string or anything that's a valid Virtual DOM element. The example creates a [Preact](https://preactjs.com/) component called `AutocompleteItem` as the template for each item to display. + +```jsx title="JSX" +/** @jsx h */ +import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch'; +import { h } from 'preact'; + +const searchClient = algoliasearch( + 'BH4D9OD16A', + 'a5c3ccfd361b8bcb9708e679c43ae0e5' +); + +function AutocompleteItem({ hit, breadcrumb }) { + return ( + +
    +
    {hit.hierarchy[hit.type]}
    +
    {breadcrumb.join(' • ')}
    +
    +
    + ); +} + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search documentation', + openOnFocus: true, + getSources({ query }) { + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: 'autocomplete', + query, + params: { + hitsPerPage: 10, + }, + }, + ], + }); + }, + templates: { + item({ item }) { + return AutocompleteItem({ + hit: item, + breadcrumb: Object.values(item.hierarchy) + .filter(Boolean) + .slice(0, -1), + }); + }, + }, + }, + ]; + }, +}); +``` + +The template displays the section name from the deepest level of `item.hierachy`. Beneath that, it displays a breadcrumb from all the higher levels in `item.hierarchy`. + +This is what the JSON record looks like: + +```json title="JSON record" +{ + "hierarchy": { + "lvl0": "The Basics", + "lvl1": "Getting Started", + "lvl2": "Defining how to display items", + "lvl3": null, + "lvl4": null, + "lvl5": null, + "lvl6": null + }, + "type": "lvl2", + "url": "https://autocomplete.algolia.com/docs/getting-started/" +} ``` + +Check out how the template displays items by searching in the input below: + + { + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: 'autocomplete', + query, + params: { + hitsPerPage: 5 + } + } + ] + }); + }, + templates: { + item({ item }) { + return ( + + ); + } + }, + }, + ]; + }} +/> + +This is all you need for a basic implementation. To go further, you can use the [`getItemUrl`](sources#getitemurl) to add [keyboard accessibility](keyboard-navigation) features. It lets users open items directly from the autocomplete menu. + +```jsx title="JSX" +/** @jsx h */ +import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js'; +import algoliasearch from 'algoliasearch'; +import { h } from 'preact'; + +const searchClient = algoliasearch( + 'BH4D9OD16A', + 'a5c3ccfd361b8bcb9708e679c43ae0e5' +); + +function AutocompleteItem({ hit, breadcrumb }) { + // ... +} + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search documentation', + openOnFocus: true, + getSources({ query }) { + return [ + { + getItems() { + // ... + }, + templates: { + // ... + }, + getItemUrl({ item }) { + return item.url; + }, + }, + ]; + }, +}); +``` +Now give it a try: navigate to one of the items using your keyboard and hitting Enter. + + { + return [ + { + getItems() { + return getAlgoliaHits({ + searchClient, + queries: [ + { + indexName: 'autocomplete', + query, + params: { + hitsPerPage: 5 + } + } + ] + }); + }, + getItemUrl({ item }) { + return item.url; + }, + templates: { + item({ item }) { + return ( + + ); + } + }, + }, + ]; + }} +/> + +## Next steps + +This outlines a basic autocomplete implementation. There's a lot more you can do, like [adding multiple sources](creating-multi-source-autocompletes), using [templates for headers, footers](templates#rendering-a-header-and-footer), or when there's [no results](templates#rendering-an-empty-state). To learn about customization options, read the [**Core Concepts**](basic-options) or follow one of the [**Guides**](using-query-suggestions-plugin). From 47d876f427f89f11dbe5fd70ad7769ff12f6bb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Fri, 5 Feb 2021 13:45:52 +0100 Subject: [PATCH 22/22] chore: fix merge --- examples/js/app.ts | 94 ------------------- examples/js/app.tsx | 66 ++++++++++--- .../src/__tests__/autocomplete.test.ts | 6 +- packages/autocomplete-js/src/autocomplete.ts | 4 - 4 files changed, 57 insertions(+), 113 deletions(-) delete mode 100644 examples/js/app.ts diff --git a/examples/js/app.ts b/examples/js/app.ts deleted file mode 100644 index 14402393e..000000000 --- a/examples/js/app.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - autocomplete, - getAlgoliaHits, - reverseHighlightHit, -} from '@algolia/autocomplete-js'; -import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; -import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; -import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; -import algoliasearch from 'algoliasearch'; -import insightsClient from 'search-insights'; - -import '@algolia/autocomplete-theme-classic'; - -const appId = 'latency'; -const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; -const searchClient = algoliasearch(appId, apiKey); -insightsClient('init', { appId, apiKey }); - -const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ insightsClient }); -const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ - key: 'search', - limit: 3, -}); -const querySuggestionsPlugin = createQuerySuggestionsPlugin({ - searchClient, - indexName: 'instant_search_demo_query_suggestions', - getSearchParams() { - return recentSearchesPlugin.data.getAlgoliaSearchParams({ - clickAnalytics: true, - }); - }, -}); - -autocomplete({ - container: '#autocomplete', - placeholder: 'Search', - openOnFocus: true, - plugins: [ - algoliaInsightsPlugin, - recentSearchesPlugin, - querySuggestionsPlugin, - ], - getSources({ query }) { - if (!query) { - return []; - } - - return [ - { - getItems() { - return getAlgoliaHits({ - searchClient, - queries: [{ indexName: 'instant_search', query }], - }); - }, - templates: { - item({ item, root }) { - const itemContent = document.createElement('div'); - const ItemSourceIcon = document.createElement('div'); - const itemTitle = document.createElement('div'); - const sourceIcon = document.createElement('img'); - - sourceIcon.width = 20; - sourceIcon.height = 20; - sourceIcon.src = item.image; - - ItemSourceIcon.classList.add('aa-ItemSourceIcon'); - ItemSourceIcon.appendChild(sourceIcon); - - itemTitle.innerHTML = reverseHighlightHit({ - hit: item, - attribute: 'name', - }); - itemTitle.classList.add('aa-ItemTitle'); - - itemContent.classList.add('aa-ItemContent'); - itemContent.appendChild(ItemSourceIcon); - itemContent.appendChild(itemTitle); - - root.appendChild(itemContent); - }, - empty({ root }) { - const itemContent = document.createElement('div'); - - itemContent.innerHTML = 'No results for this query'; - itemContent.classList.add('aa-ItemContent'); - - root.appendChild(itemContent); - }, - }, - }, - ]; - }, -}); diff --git a/examples/js/app.tsx b/examples/js/app.tsx index 2145bffdd..a03594667 100644 --- a/examples/js/app.tsx +++ b/examples/js/app.tsx @@ -2,19 +2,25 @@ import { autocomplete, getAlgoliaHits, - highlightHit, + snippetHit, } from '@algolia/autocomplete-js'; import { createAlgoliaInsightsPlugin } from '@algolia/autocomplete-plugin-algolia-insights'; import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'; import { Hit } from '@algolia/client-search'; import algoliasearch from 'algoliasearch'; -import { h } from 'preact'; +import { h, Fragment } from 'preact'; import insightsClient from 'search-insights'; import '@algolia/autocomplete-theme-classic'; -type Product = { name: string; image: string }; +import { shortcutsPlugin } from './shortcutsPlugin'; + +type Product = { + name: string; + image: string; + description: string; +}; type ProductHit = Hit; const appId = 'latency'; @@ -44,6 +50,7 @@ autocomplete({ debug: true, openOnFocus: true, plugins: [ + shortcutsPlugin, algoliaInsightsPlugin, recentSearchesPlugin, querySuggestionsPlugin, @@ -59,16 +66,38 @@ autocomplete({ getItems() { return getAlgoliaHits({ searchClient, - queries: [{ indexName: 'instant_search', query }], + queries: [ + { + indexName: 'instant_search', + query, + params: { + clickAnalytics: true, + attributesToSnippet: ['name:10', 'description:35'], + snippetEllipsisText: '…', + }, + }, + ], }); }, templates: { + header() { + return ( + + Products +
    +
    + ); + }, item({ item }) { return ; }, empty() { return ( -
    No results for this query.
    +
    +
    + No products for this query. +
    +
    ); }, }, @@ -83,14 +112,27 @@ type ProductItemProps = { function ProductItem({ hit }: ProductItemProps) { return ( -
    -
    - {hit.name} + +
    + {hit.name}
    - -
    - {highlightHit({ hit, attribute: 'name' })} +
    +
    + {snippetHit({ hit, attribute: 'name' })} +
    +
    + {snippetHit({ hit, attribute: 'description' })} +
    -
    + +
    ); } diff --git a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts index 50e374640..5bd5c03ef 100644 --- a/packages/autocomplete-js/src/__tests__/autocomplete.test.ts +++ b/packages/autocomplete-js/src/__tests__/autocomplete.test.ts @@ -93,8 +93,8 @@ describe('autocomplete-js', () => { stroke-dasharray="164.93361431346415 56.97787143782138" stroke-width="6" > - - + + { type="rotate" values="0 50 50;90 50 50;180 50 50;360 50 50" /> - + diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 6d5f69b05..0701c0e4d 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -144,10 +144,6 @@ export function autocomplete( state: lastStateRef.current, }; - hasEmptySourceTemplateRef.current = renderProps.state.collections.some( - (collection) => collection.source.templates.empty - ); - const render = (!getItemsCount(state) && !hasEmptySourceTemplateRef.current &&