オートコンプリート

1import {
2  AutocompleteState,
3  createAutocomplete,
4} from '@algolia/autocomplete-core';
5import { getAlgoliaResults } from '@algolia/autocomplete-js';
6import classNames from 'classnames';
7import { useRouter } from 'next/router';
8import { useMemo, useRef, useState } from 'react';
9import { searchClient } from '../algolia/client';
10import { Post } from '../types/post';
11
12const SearchAutocomplete = () => {
13  const router = useRouter();
14  const inputRef = useRef<HTMLInputElement>(null);
15  const [autocompleteState, setAutocompleteState] =
16    useState<AutocompleteState<Post>>();
17  const autocomplete = useMemo(
18    () =>
19      createAutocomplete<Post>({
20        id: 'autocomplete-search',
21        openOnFocus: true,
22        onStateChange({ state }) {
23          setAutocompleteState(state);
24        },
25        onSubmit(params) {
26          alert(`実際には「${params.state.query}」の検索結果画面に遷移します`);
27        },
28        getSources() {
29          return [
30            {
31              sourceId: 'posts',
32              getItemInputValue({ item }) {
33                return item.title;
34              },
35              getItems({ query }) {
36                return getAlgoliaResults({
37                  searchClient,
38                  queries: [
39                    {
40                      indexName: 'posts',
41                      query,
42                      params: {
43                        hitsPerPage: 4,
44                      },
45                    },
46                  ],
47                });
48              },
49              getItemUrl({ item }) {
50                return `/${router.query.id}?postId=${item.id}`;
51              },
52              onSelect(params) {
53                router.replace(params.itemUrl as string, undefined, {
54                  shallow: true,
55                });
56              },
57            },
58          ];
59        },
60        navigator: {
61          navigate({ itemUrl }) {
62            router.push(itemUrl);
63          },
64        },
65      }),
66    []
67  );
68
69  return (
70    <div {...autocomplete.getRootProps({})}>
71      <form
72        {...(autocomplete.getFormProps({
73          inputElement: inputRef.current,
74        }) as any)}
75      >
76        <input
77          {...(autocomplete.getInputProps({
78            inputElement: inputRef.current,
79          }) as any)}
80          id="search-field"
81          className="block w-full bg-transparent rounded border mb-2"
82          placeholder="投稿を検索"
83          autoComplete="off"
84          ref={inputRef}
85          type="text"
86        />
87
88        <div {...(autocomplete.getPanelProps({}) as any)}>
89          {autocompleteState?.isOpen &&
90            autocompleteState?.collections.map((collection, index) => {
91              const { source, items } = collection;
92
93              return (
94                <div
95                  className="rounded overflow-hidden dark:bg-slate-800 bg-white shadow w-full"
96                  key={`source-${index}`}
97                >
98                  {items.length > 0 && (
99                    <ul {...autocomplete.getListProps()}>
100                      {items.map((item, index) => (
101                        <li key={item.title}>
102                          <a
103                            {...(autocomplete.getItemProps({
104                              item,
105                              source,
106                            }) as any)}
107                            className={classNames(
108                              'block py-2 pl-3 text-sm',
109                              autocompleteState?.activeItemId === index &&
110                                'text-white bg-indigo-600'
111                            )}
112                          >
113                            {item.title}
114                          </a>
115                        </li>
116                      ))}
117                    </ul>
118                  )}
119                </div>
120              );
121            })}
122        </div>
123      </form>
124    </div>
125  );
126};
127
128export default SearchAutocomplete;