diff --git a/.circleci/config.yml b/.circleci/config.yml index d7c6792f7..cfd5014b2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ aliases: mkdir -p packages/autocomplete-plugin-algolia-insights/dist mkdir -p packages/autocomplete-plugin-recent-searches/dist mkdir -p packages/autocomplete-plugin-query-suggestions/dist + mkdir -p packages/autocomplete-layout-classic/dist cp -R /tmp/workspace/packages/autocomplete-shared/dist packages/autocomplete-shared cp -R /tmp/workspace/packages/autocomplete-core/dist packages/autocomplete-core @@ -38,6 +39,7 @@ aliases: cp -R /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist packages/autocomplete-plugin-algolia-insights cp -R /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist packages/autocomplete-plugin-recent-searches cp -R /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist packages/autocomplete-plugin-query-suggestions + cp -R /tmp/workspace/packages/autocomplete-layout-classic/dist packages/autocomplete-layout-classic defaults: &defaults working_directory: ~/autocomplete @@ -82,6 +84,7 @@ jobs: mkdir -p /tmp/workspace/packages/autocomplete-plugin-algolia-insights/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-recent-searches/dist mkdir -p /tmp/workspace/packages/autocomplete-plugin-query-suggestions/dist + mkdir -p /tmp/workspace/packages/autocomplete-layout-classic/dist cp -R packages/autocomplete-shared/dist /tmp/workspace/packages/autocomplete-shared cp -R packages/autocomplete-core/dist /tmp/workspace/packages/autocomplete-core @@ -90,6 +93,7 @@ jobs: cp -R packages/autocomplete-plugin-algolia-insights/dist /tmp/workspace/packages/autocomplete-plugin-algolia-insights cp -R packages/autocomplete-plugin-recent-searches/dist /tmp/workspace/packages/autocomplete-plugin-recent-searches cp -R packages/autocomplete-plugin-query-suggestions/dist /tmp/workspace/packages/autocomplete-plugin-query-suggestions + cp -R packages/autocomplete-layout-classic/dist /tmp/workspace/packages/autocomplete-layout-classic - persist_to_workspace: root: *workspace_root paths: diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index c311604a3..78cf009f7 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -4,6 +4,7 @@ "^": "buildCommand is false because `yarn prepare` is going to build packages anyway.", "sandboxes": [ "/examples/github-repositories-custom-plugin", + "/examples/layouts", "/examples/playground", "/examples/query-suggestions-with-categories", "/examples/query-suggestions-with-hits", diff --git a/bundlesize.config.json b/bundlesize.config.json index be73143ad..d1b3c3e2e 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -24,6 +24,10 @@ "path": "packages/autocomplete-plugin-query-suggestions/dist/umd/index.production.js", "maxSize": "4 kB" }, + { + "path": "packages/autocomplete-layout-classic/dist/umd/index.production.js", + "maxSize": "4.25 kB" + }, { "path": "packages/autocomplete-theme-classic/dist/theme.css", "maxSize": "4.25 kB" diff --git a/examples/layouts/README.md b/examples/layouts/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/examples/layouts/app.tsx b/examples/layouts/app.tsx new file mode 100644 index 000000000..8bebe2e92 --- /dev/null +++ b/examples/layouts/app.tsx @@ -0,0 +1,58 @@ +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { + NavigationCommands, + SearchByAlgolia, +} from '@algolia/autocomplete-layout-classic'; +import { createQuerySuggestionsPlugin } from '@algolia/autocomplete-plugin-query-suggestions'; +import algoliasearch from 'algoliasearch/lite'; +import { h, render } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +const appId = 'latency'; +const apiKey = '6be0576ff61c053d5f9a3225e2a90f76'; +const searchClient = algoliasearch(appId, apiKey); + +const querySuggestionsPlugin = createQuerySuggestionsPlugin({ + searchClient, + indexName: 'instant_search_demo_query_suggestions', + getSearchParams() { + return { + hitsPerPage: 10, + }; + }, +}); + +autocomplete({ + container: '#autocomplete', + placeholder: 'Search', + openOnFocus: true, + plugins: [querySuggestionsPlugin], + components: { + NavigationCommands, + SearchByAlgolia, + }, + render({ sections, Fragment, components }, root) { + render( + +
{sections}
+ +
, + root + ); + }, +}); diff --git a/examples/layouts/env.ts b/examples/layouts/env.ts new file mode 100644 index 000000000..e289f63b2 --- /dev/null +++ b/examples/layouts/env.ts @@ -0,0 +1,10 @@ +import * as preact from 'preact'; + +// Parcel picks the `source` field of the monorepo packages and thus doesn't +// apply the Babel config to replace our `__DEV__` global expression. +// We therefore need to manually override it in the example app. +// See https://twitter.com/devongovett/status/1134231234605830144 +(global as any).__DEV__ = process.env.NODE_ENV !== 'production'; +(global as any).__TEST__ = false; +(global as any).h = preact.h; +(global as any).React = preact; diff --git a/examples/layouts/favicon.png b/examples/layouts/favicon.png new file mode 100644 index 000000000..f305dca76 Binary files /dev/null and b/examples/layouts/favicon.png differ diff --git a/examples/layouts/index.html b/examples/layouts/index.html new file mode 100644 index 000000000..df0fe276a --- /dev/null +++ b/examples/layouts/index.html @@ -0,0 +1,20 @@ + + + + + + + + + Layouts Examples + + + +
+
+
+ + + + + diff --git a/examples/layouts/package.json b/examples/layouts/package.json new file mode 100644 index 000000000..a6c9e4b6e --- /dev/null +++ b/examples/layouts/package.json @@ -0,0 +1,28 @@ +{ + "name": "@algolia/autocomplete-example-layouts", + "description": "Autocomplete layouts example", + "version": "1.0.0-alpha.44", + "private": true, + "license": "MIT", + "main": "index.html", + "scripts": { + "build": "parcel build index.html", + "start": "parcel index.html" + }, + "dependencies": { + "@algolia/autocomplete-js": "1.0.0-alpha.44", + "@algolia/autocomplete-plugin-query-suggestions": "1.0.0-alpha.44", + "@algolia/autocomplete-theme-classic": "1.0.0-alpha.44", + "@algolia/autocomplete-layout-classic": "1.0.0-alpha.44", + "algoliasearch": "4.8.6", + "preact": "10.5.13" + }, + "devDependencies": { + "parcel-bundler": "1.12.4" + }, + "keywords": [ + "algolia", + "autocomplete", + "javascript" + ] +} diff --git a/examples/layouts/style.css b/examples/layouts/style.css new file mode 100644 index 000000000..a4d3906cf --- /dev/null +++ b/examples/layouts/style.css @@ -0,0 +1,20 @@ +* { + box-sizing: border-box; +} + +body { + background-color: rgb(244, 244, 249); + color: rgb(65, 65, 65); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding: 1rem; +} + +.container { + margin: 0 auto; + max-width: 640px; + width: 100%; +} diff --git a/packages/autocomplete-layout-classic/README.md b/packages/autocomplete-layout-classic/README.md new file mode 100644 index 000000000..257316f4e --- /dev/null +++ b/packages/autocomplete-layout-classic/README.md @@ -0,0 +1,15 @@ +# @algolia/autocomplete-layout-classic + +Classic layout for Algolia Autocomplete. + +## Installation + +```sh +yarn add @algolia/autocomplete-layout-classic@alpha +# or +npm install @algolia/autocomplete-layout-classic@alpha +``` + +## Documentation + +[Read documentation →](https://autocomplete.algolia.com/docs/autocomplete-layout-classic) diff --git a/packages/autocomplete-layout-classic/package.json b/packages/autocomplete-layout-classic/package.json new file mode 100644 index 000000000..4c267a01a --- /dev/null +++ b/packages/autocomplete-layout-classic/package.json @@ -0,0 +1,39 @@ +{ + "name": "@algolia/autocomplete-layout-classic", + "description": "Classic layout for Algolia Autocomplete.", + "version": "1.0.0-alpha.44", + "license": "MIT", + "homepage": "https://github.com/algolia/autocomplete", + "repository": "algolia/autocomplete", + "author": { + "name": "Algolia, Inc.", + "url": "https://www.algolia.com" + }, + "sideEffects": false, + "files": [ + "dist/" + ], + "source": "src/index.ts", + "types": "dist/esm/index.d.ts", + "module": "dist/esm/index.js", + "main": "dist/umd/index.production.js", + "umd:main": "dist/umd/index.production.js", + "unpkg": "dist/umd/index.production.js", + "jsdelivr": "dist/umd/index.production.js", + "scripts": { + "build:clean": "rm -rf ./dist", + "build:esm": "babel src --root-mode upward --extensions '.ts,.tsx' --out-dir dist/esm --ignore '**/*/__tests__/'", + "build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/esm", + "build:umd": "rollup --config", + "build": "yarn build:clean && yarn build:umd && yarn build:esm && yarn build:types", + "on:change": "concurrently \"yarn build:esm\" \"yarn build:types\"", + "prepare": "yarn build:esm && yarn build:types", + "watch": "watch \"yarn on:change\" --ignoreDirectoryPattern \"/dist/\"" + }, + "dependencies": { + "preact": "^10.0.0" + }, + "devDependencies": { + "@algolia/autocomplete-js": "1.0.0-alpha.44" + } +} diff --git a/packages/autocomplete-layout-classic/rollup.config.js b/packages/autocomplete-layout-classic/rollup.config.js new file mode 100644 index 000000000..099ce0e3a --- /dev/null +++ b/packages/autocomplete-layout-classic/rollup.config.js @@ -0,0 +1,5 @@ +import { createRollupConfigs } from '../../scripts/rollup/config'; + +import pkg from './package.json'; + +export default createRollupConfigs({ pkg }); diff --git a/packages/autocomplete-layout-classic/src/NavigationCommands.d.ts b/packages/autocomplete-layout-classic/src/NavigationCommands.d.ts new file mode 100644 index 000000000..9538171cb --- /dev/null +++ b/packages/autocomplete-layout-classic/src/NavigationCommands.d.ts @@ -0,0 +1,7 @@ +import { NavigationCommandsProps } from './NavigationCommands'; + +declare module '@algolia/autocomplete-js' { + export interface AutocompleteComponents { + NavigationCommands: (props: NavigationCommandsProps) => JSX.Element; + } +} diff --git a/packages/autocomplete-layout-classic/src/NavigationCommands.tsx b/packages/autocomplete-layout-classic/src/NavigationCommands.tsx new file mode 100644 index 000000000..57e0bb4cd --- /dev/null +++ b/packages/autocomplete-layout-classic/src/NavigationCommands.tsx @@ -0,0 +1,109 @@ +/** @jsx createElement */ +import type { AutocompleteRenderer } from '@algolia/autocomplete-js'; +import { + createElement as preactCreateElement, + Fragment as PreactFragment, +} from 'preact'; + +type NavigationCommandsTranslations = { + toSelect: string; + toNavigate: string; + toClose: string; +}; + +const defaultTranslations: NavigationCommandsTranslations = { + toSelect: 'to select', + toNavigate: 'to navigate', + toClose: 'to close', +}; + +export type NavigationCommandsProps = { + translations?: NavigationCommandsTranslations; +}; + +export function createNavigationCommandsComponent({ + createElement, +}: AutocompleteRenderer) { + return function NavigationCommands({ + translations = defaultTranslations, + }: NavigationCommandsProps): JSX.Element { + return ( +
+ +
+ ); + }; +} + +export const NavigationCommands = createNavigationCommandsComponent({ + createElement: preactCreateElement, + Fragment: PreactFragment, +}); diff --git a/packages/autocomplete-layout-classic/src/SearchByAlgolia.d.ts b/packages/autocomplete-layout-classic/src/SearchByAlgolia.d.ts new file mode 100644 index 000000000..97864841c --- /dev/null +++ b/packages/autocomplete-layout-classic/src/SearchByAlgolia.d.ts @@ -0,0 +1,7 @@ +import { SearchByAlgoliaProps } from './SearchByAlgolia'; + +declare module '@algolia/autocomplete-js' { + export interface AutocompleteComponents { + SearchByAlgolia: (props: SearchByAlgoliaProps) => JSX.Element; + } +} diff --git a/packages/autocomplete-layout-classic/src/SearchByAlgolia.tsx b/packages/autocomplete-layout-classic/src/SearchByAlgolia.tsx new file mode 100644 index 000000000..0a49981e4 --- /dev/null +++ b/packages/autocomplete-layout-classic/src/SearchByAlgolia.tsx @@ -0,0 +1,54 @@ +/** @jsx createElement */ +import type { AutocompleteRenderer } from '@algolia/autocomplete-js'; +import { + createElement as preactCreateElement, + Fragment as PreactFragment, +} from 'preact'; + +type SearchByAlgoliaTranslations = { + searchBy: string; +}; + +const defaultTranslations: SearchByAlgoliaTranslations = { + searchBy: 'Search by', +}; + +export type SearchByAlgoliaProps = { + translations?: SearchByAlgoliaTranslations; +}; + +export function createSearchByAlgoliaComponent({ + createElement, +}: AutocompleteRenderer) { + return function NavigationCommands({ + translations = defaultTranslations, + }: SearchByAlgoliaProps): JSX.Element { + return ( + + {translations.searchBy} + + + + + ); + }; +} + +export const SearchByAlgolia = createSearchByAlgoliaComponent({ + createElement: preactCreateElement, + Fragment: PreactFragment, +}); diff --git a/packages/autocomplete-layout-classic/src/index.ts b/packages/autocomplete-layout-classic/src/index.ts new file mode 100644 index 000000000..acaf27b00 --- /dev/null +++ b/packages/autocomplete-layout-classic/src/index.ts @@ -0,0 +1,2 @@ +export * from './NavigationCommands'; +export * from './SearchByAlgolia'; diff --git a/packages/autocomplete-layout-classic/tsconfig.declaration.json b/packages/autocomplete-layout-classic/tsconfig.declaration.json new file mode 100644 index 000000000..1e0c6449f --- /dev/null +++ b/packages/autocomplete-layout-classic/tsconfig.declaration.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.declaration" +} diff --git a/packages/autocomplete-theme-classic/src/theme.scss b/packages/autocomplete-theme-classic/src/theme.scss index c62c7c3a1..46f02fb4f 100644 --- a/packages/autocomplete-theme-classic/src/theme.scss +++ b/packages/autocomplete-theme-classic/src/theme.scss @@ -10,6 +10,7 @@ // 9. Detached Mode // 10. Gradients // 11. Utilities +// 12. Layouts // ---------------- // Note: @@ -80,9 +81,16 @@ --aa-overlay-color-rgb: 115, 114, 129; --aa-overlay-color-alpha: 0.4; - // Shadows + // Shadows & Gradients --aa-panel-shadow: 0 0 0 1px rgba(35, 38, 59, 0.1), 0 6px 16px -4px rgba(35, 38, 59, 0.15); + --aa-key-shadow: inset 0 -2px 0 0 rgba(205, 205, 230, 1), + inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4); + --aa-key-background: linear-gradient( + -225deg, + rgb(213, 219, 228), + rgb(248, 248, 248) + ); // Scrollbar --aa-scrollbar-width: 13px; @@ -108,7 +116,7 @@ body { // Text colors --aa-text-color-rgb: 183, 192, 199; --aa-primary-color-rgb: 146, 138, 255; - --aa-muted-color-rgb: 146, 138, 255; + --aa-muted-color-rgb: 136, 138, 155; // Background colors --aa-input-background-color-rgb: 0, 3, 9; @@ -124,6 +132,13 @@ body { // Shadows --aa-panel-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), 0 3px 8px 0 rgb(0, 3, 9); + --aa-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), + inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3); + --aa-key-background: linear-gradient( + -26.5deg, + rgb(86, 88, 114), + rgb(49, 53, 91) + ); // Scrollbar --aa-scrollbar-track-background-color-rgb: 44, 46, 64; @@ -768,13 +783,13 @@ body { rgba(var(--aa-muted-color-rgb), var(--aa-muted-color-alpha)) ); content: ''; - height: var(--aa-spacing); + height: var(--aa-spacing-half); left: 0; opacity: 0.12; pointer-events: none; position: absolute; right: 0; - top: calc(var(--aa-spacing) * -1); + top: calc(var(--aa-spacing-half) * -1); z-index: calc(var(--aa-base-z-index) - 1); } } @@ -976,3 +991,61 @@ body { display: none; } } + +//---------------- +// 12. Layouts +//---------------- +@media (hover: none) and (pointer: coarse) { + .aa-NavigationCommands > * { + display: none; + } +} + +.aa-NavigationCommandList { + display: grid; + gap: var(--aa-spacing); + grid-template-columns: repeat(3, auto); + margin: 0; + padding: 0; +} + +.aa-NavigationCommandListItem { + align-items: center; + display: flex; + flex-direction: row; + justify-content: center; +} + +.aa-NavigationCommandLabel, +.aa-SearchByAlgoliaLabel { + color: rgba(var(--aa-muted-color-rgb),1); + font-size: 0.75em; + line-height: 1.6em; +} + +.aa-SearchByAlgolia { + align-items: center; + display: grid; + gap: var(--aa-spacing-half); + grid-template-columns: repeat(2, auto); + text-decoration: none; +} + +.aa-SearchByAlgoliaLogo { + color: rgb(84, 104, 255); +} + +.aa-Key { + align-items: center; + background: var(--aa-key-background); + border-radius: 3px; + box-shadow: var(--aa-key-shadow); + color: rgba(var(--aa-muted-color-rgb),1); + display: flex; + height: 20px; + justify-content: center; + margin-right: 8px; + position: relative; + top: -1px; + width: 20px; +} diff --git a/packages/website/docs/autocomplete-js.md b/packages/website/docs/autocomplete-js.md index 90ab3fb52..baaf9f7c0 100644 --- a/packages/website/docs/autocomplete-js.md +++ b/packages/website/docs/autocomplete-js.md @@ -303,7 +303,7 @@ autocomplete({ > `(type: any, props: Record | null, ...children: ComponentChildren[]) => VNode` | defaults to `preact.createElement` -The function that create virtual nodes. +The function to create virtual nodes. It uses [Preact 10](https://preactjs.com/guide/v10/whats-new/)'s `createElement` by default, but you can provide your own implementation. diff --git a/packages/website/docs/autocomplete-layout-classic.md b/packages/website/docs/autocomplete-layout-classic.md new file mode 100644 index 000000000..f41093fb3 --- /dev/null +++ b/packages/website/docs/autocomplete-layout-classic.md @@ -0,0 +1,210 @@ +--- +id: autocomplete-layout-classic +--- + +The Classic layout provides components for Autocomplete experiences. + +This layout relies on the [Autocomplete Classic Theme](autocomplete-theme-classic). Make sure to install it as well. + +## Installation + +First, you need to install the layout. + +```bash +yarn add @algolia/autocomplete-layout-classic@alpha +# or +npm install @algolia/autocomplete-layout-classic@alpha +``` + +Then import it in your project: + +```js +import { + NavigationCommands, + SearchByAlgolia, +} from '@algolia/autocomplete-layout-classic'; +``` + +If you don't use a package manager, you can use a standalone endpoint: + +```html + +``` + +## Examples + +With default translations: + +```tsx +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { + NavigationCommands, + SearchByAlgolia, +} from '@algolia/autocomplete-layout-classic'; +import { h, render } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +autocomplete({ + // ... + components: { + NavigationCommands, + SearchByAlgolia, + }, + render({ sections, Fragment, components }, root) { + render( + +
{sections}
+
+ + +
+
, + root + ); + }, +}); +``` + +With French translations: + +```tsx +/** @jsx h */ +import { autocomplete } from '@algolia/autocomplete-js'; +import { + NavigationCommands, + SearchByAlgolia, +} from '@algolia/autocomplete-layout-classic'; +import { h, render } from 'preact'; + +import '@algolia/autocomplete-theme-classic'; + +autocomplete({ + // ... + components: { + NavigationCommands, + SearchByAlgolia, + }, + render({ sections, Fragment, components }, root) { + render( + +
{sections}
+
+ + +
+
, + root + ); + }, +}); +``` + +With a custom renderer: + +```tsx +import { autocomplete } from '@algolia/autocomplete-js'; +import { + createNavigationCommandsComponent, + createSearchByAlgoliaComponent, +} from '@algolia/autocomplete-layout-classic'; +import React, { createElement, Fragment } from 'react'; + +import '@algolia/autocomplete-theme-classic'; + +const renderer = { createElement, Fragment }; + +autocomplete({ + // ... + renderer, + components: { + NavigationCommands: createNavigationCommandsComponent(renderer), + SearchByAlgolia: createSearchByAlgoliaComponent(renderer), + }, + render({ sections, Fragment, components }, root) { + render( + +
{sections}
+ +
, + root + ); + }, +}); +``` + +## Reference + +### `NavigationCommands` + +#### `translations` + +> `NavigationCommandsTranslations` | defaults to English strings + +The translations to display. + +```ts +type NavigationCommandsTranslations = { + toSelect: string; + toNavigate: string; + toClose: string; +}; +``` + +Defaults to: + +```ts +const translations = { + toSelect: 'to select', + toNavigate: 'to navigate', + toClose: 'to close', +}; +``` + +### `SearchByAlgolia` + +#### `translations` + +> `SearchByAlgoliaTranslations` | defaults to English strings + +The translations to display. + +```ts +type SearchByAlgoliaTranslations = { + searchBy: string; +}; +``` + +Defaults to: + +```ts +const translations = { + searchBy: 'Search by', +}; +``` + +### `createNavigationCommandsComponent` + +> `(renderer: AutocompleteRenderer) => JSX.Element` + +The function accepts a [renderer](/docs/autocomplete-js/#renderer) and returns the [`NavigationCommands`](#navigationcommands) component. It's useful when using a framework like [React](/docs/using-react) or [Vue](/docs/using-vue). + +### `createSearchByAlgoliaComponent` + +> `(renderer: AutocompleteRenderer) => JSX.Element` + +The function accepts a [renderer](/docs/autocomplete-js/#renderer) and returns the [`SearchByAlgolia`](#searchbyalgolia) component. It's useful when using a framework like [React](/docs/using-react) or [Vue](/docs/using-vue). diff --git a/packages/website/sidebars.js b/packages/website/sidebars.js index 1862906a2..d213c598f 100644 --- a/packages/website/sidebars.js +++ b/packages/website/sidebars.js @@ -75,6 +75,7 @@ module.exports = { ], }, 'autocomplete-theme-classic', + 'autocomplete-layout-classic', ], }, }; diff --git a/ship.config.js b/ship.config.js index beaa5e8ed..57b6cdc44 100644 --- a/ship.config.js +++ b/ship.config.js @@ -9,6 +9,7 @@ module.exports = { packagesToPublish: [ 'packages/autocomplete-core', 'packages/autocomplete-js', + 'packages/autocomplete-layout-classic', 'packages/autocomplete-plugin-algolia-insights', 'packages/autocomplete-plugin-query-suggestions', 'packages/autocomplete-plugin-recent-searches',