diff --git a/packages/instantsearch-ui-components/src/components/autocomplete/Autocomplete.tsx b/packages/instantsearch-ui-components/src/components/autocomplete/Autocomplete.tsx new file mode 100644 index 0000000000..e8fe864462 --- /dev/null +++ b/packages/instantsearch-ui-components/src/components/autocomplete/Autocomplete.tsx @@ -0,0 +1,35 @@ +/** @jsx createElement */ + +import { cx } from '../../lib/cx'; + +import type { ComponentChildren, Renderer } from '../../types'; + +export type AutocompleteProps = { + isOpen: boolean; + children?: ComponentChildren; + classNames?: Partial; +}; + +export type AutocompleteClassNames = { + /** + * Class names to apply to the root element + */ + root: string | string[]; +}; + +export function createAutocompleteComponent({ createElement }: Renderer) { + return function Autocomplete(userProps: AutocompleteProps) { + const { children, isOpen, classNames = {} } = userProps; + + return ( +
+ {children} +
+ ); + }; +} diff --git a/packages/instantsearch-ui-components/src/components/autocomplete/AutocompleteIndex.tsx b/packages/instantsearch-ui-components/src/components/autocomplete/AutocompleteIndex.tsx new file mode 100644 index 0000000000..b034ba3deb --- /dev/null +++ b/packages/instantsearch-ui-components/src/components/autocomplete/AutocompleteIndex.tsx @@ -0,0 +1,49 @@ +/** @jsx createElement */ + +import { cx } from '../../lib/cx'; + +import type { Renderer } from '../../types'; + +export type AutocompleteIndexProps< + T = { objectID: string } & Record +> = { + items: T[]; + ItemComponent: (item: T) => JSX.Element; + classNames?: Partial; +}; + +export type AutocompleteIndexClassNames = { + /** + * Class names to apply to the root element + **/ + root: string | string[]; + /** + * Class names to apply to the list element + */ + list: string | string[]; + /** + * Class names to apply to each item element + */ + item: string | string[]; +}; + +export function createAutocompleteIndexComponent({ createElement }: Renderer) { + return function AutocompleteIndex(userProps: AutocompleteIndexProps) { + const { items, ItemComponent, classNames = {} } = userProps; + + return ( +
+
    + {items.map((item) => ( +
  1. + +
  2. + ))} +
+
+ ); + }; +} diff --git a/packages/instantsearch-ui-components/src/components/autocomplete/AutocompletePanel.tsx b/packages/instantsearch-ui-components/src/components/autocomplete/AutocompletePanel.tsx new file mode 100644 index 0000000000..641c60ca50 --- /dev/null +++ b/packages/instantsearch-ui-components/src/components/autocomplete/AutocompletePanel.tsx @@ -0,0 +1,39 @@ +/** @jsx createElement */ + +import { cx } from '../../lib/cx'; + +import type { ComponentChildren, Renderer } from '../../types'; + +export type AutocompletePanelProps = { + isOpen: boolean; + children?: ComponentChildren; + classNames?: Partial; +}; + +export type AutocompletePanelClassNames = { + /** + * Class names to apply to the root element + */ + root: string | string[]; + /** + * Class names to apply to the layout element + */ + layout: string | string[]; +}; + +export function createAutocompletePanelComponent({ createElement }: Renderer) { + return function AutocompletePanel(userProps: AutocompletePanelProps) { + const { children, isOpen, classNames = {} } = userProps; + + return ( + + ); + }; +} diff --git a/packages/instantsearch-ui-components/src/components/autocomplete/index.ts b/packages/instantsearch-ui-components/src/components/autocomplete/index.ts new file mode 100644 index 0000000000..daf5e34679 --- /dev/null +++ b/packages/instantsearch-ui-components/src/components/autocomplete/index.ts @@ -0,0 +1,3 @@ +export * from './Autocomplete'; +export * from './AutocompleteIndex'; +export * from './AutocompletePanel'; diff --git a/packages/instantsearch-ui-components/src/components/index.ts b/packages/instantsearch-ui-components/src/components/index.ts index 7b8e46a147..b34f76a53f 100644 --- a/packages/instantsearch-ui-components/src/components/index.ts +++ b/packages/instantsearch-ui-components/src/components/index.ts @@ -1,3 +1,4 @@ +export * from './autocomplete'; export * from './Carousel'; export * from './chat/Chat'; export * from './chat/ChatHeader'; diff --git a/packages/react-instantsearch/src/components/Autocomplete.tsx b/packages/react-instantsearch/src/components/Autocomplete.tsx index ecce449281..89d8da9e9e 100644 --- a/packages/react-instantsearch/src/components/Autocomplete.tsx +++ b/packages/react-instantsearch/src/components/Autocomplete.tsx @@ -1,10 +1,31 @@ -import React from 'react'; -import { Index } from 'react-instantsearch-core'; +import { + createAutocompleteComponent, + createAutocompleteIndexComponent, + createAutocompletePanelComponent, +} from 'instantsearch-ui-components'; +import React, { createElement, useState, Fragment } from 'react'; +import { Index, useHits } from 'react-instantsearch-core'; -import { Hits, SearchBox } from '../widgets'; +import { SearchBox } from '../widgets/SearchBox'; +import type { Pragma } from 'instantsearch-ui-components'; import type { BaseHit, Hit } from 'instantsearch.js'; +const Autocomplete = createAutocompleteComponent({ + createElement: createElement as Pragma, + Fragment, +}); + +const AutocompletePanel = createAutocompletePanelComponent({ + createElement: createElement as Pragma, + Fragment, +}); + +const AutocompleteIndex = createAutocompleteIndexComponent({ + createElement: createElement as Pragma, + Fragment, +}); + type IndexConfig = Hit> = { indexName: string; getQuery?: (item: TItem) => string; @@ -17,14 +38,38 @@ export type AutocompleteProps = { }; export function EXPERIMENTAL_Autocomplete({ indices }: AutocompleteProps) { + const [isOpen, setIsOpen] = useState(false); + return ( - - {indices.map((index) => ( - - } /> - - ))} + + setIsOpen(true)} /> + + {indices.map((index) => ( + + + + ))} + + ); } + +type AutocompleteIndexProps = { + itemComponent: IndexConfig['itemComponent']; +}; + +function AutocompleteIndexComponent({ + itemComponent: ItemComponent, +}: AutocompleteIndexProps) { + const { items } = useHits(); + + return ( + + ); +} diff --git a/packages/react-instantsearch/src/components/__tests__/Autocomplete.test.tsx b/packages/react-instantsearch/src/components/__tests__/Autocomplete.test.tsx index 7f7becbd56..297bfd09a5 100644 --- a/packages/react-instantsearch/src/components/__tests__/Autocomplete.test.tsx +++ b/packages/react-instantsearch/src/components/__tests__/Autocomplete.test.tsx @@ -35,7 +35,7 @@ describe('Autocomplete', () => { test('should render a searchbox and indices with hits', async () => { const searchClient = createMockedSearchClient(); - render( + const { container } = render( { ); - expect(await screen.findByText('Item 1')).toBeInTheDocument(); - expect(await screen.findByText('hello')).toBeInTheDocument(); - expect(screen.getByRole('searchbox')).toBeInTheDocument(); + await screen.findByText('Item 1'); + + expect(screen.getByRole('search')).toMatchInlineSnapshot(` + + `); + + expect(container.querySelector('.ais-AutocompletePanel')) + .toMatchInlineSnapshot(` + + `); }); }); diff --git a/packages/react-instantsearch/src/components/index.ts b/packages/react-instantsearch/src/components/index.ts index c0ab19964d..f54648c28b 100644 --- a/packages/react-instantsearch/src/components/index.ts +++ b/packages/react-instantsearch/src/components/index.ts @@ -1 +1,2 @@ export * from './Carousel'; +export * from './Autocomplete';