@jetbrains/kotlin-web-site-ui 4.0.0-alpha.2 → 4.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/out/blocks/formik-wrapper/input.js +3 -3
  2. package/out/blocks/formik-wrapper/privacy-checkbox.js +17 -21
  3. package/out/blocks/formik-wrapper/privacy-notice.js +19 -28
  4. package/out/blocks/formik-wrapper/submit-button.js +3 -3
  5. package/out/components/cta-block/cta-block.js +17 -33
  6. package/out/components/footer/footer.js +30 -48
  7. package/out/components/footer/logo/logo.js +3 -5
  8. package/out/components/footer/nav/nav-item.js +11 -15
  9. package/out/components/footer/nav/nav-list.js +8 -11
  10. package/out/components/footer/social-list/social-item/social-item.js +3 -5
  11. package/out/components/footer/social-list/social-list.js +6 -7
  12. package/out/components/header/{consts.js → components/header/consts.js} +0 -0
  13. package/out/components/header/components/header/full-search/chapters/chapters.js +56 -0
  14. package/out/components/header/components/header/full-search/chapters/chapters.module.pcss.js +11 -0
  15. package/out/components/header/components/header/full-search/empty/empty.js +22 -0
  16. package/out/components/header/components/header/full-search/empty/empty.module.pcss.js +6 -0
  17. package/out/components/header/components/header/full-search/empty/full-search-empty.svg.js +1539 -0
  18. package/out/components/header/components/header/full-search/full-search.js +70 -0
  19. package/out/components/header/components/header/full-search/full-search.module.pcss.js +9 -0
  20. package/out/components/header/components/header/full-search/hit-list/hit-list.js +44 -0
  21. package/out/components/header/components/header/full-search/hit-list/hit-list.module.pcss.js +5 -0
  22. package/out/components/header/components/header/full-search/loading/loading.js +11 -0
  23. package/out/components/header/components/header/full-search/loading/loading.module.pcss.js +4 -0
  24. package/out/components/header/components/header/full-search/result/get-extended-hits.js +55 -0
  25. package/out/components/header/components/header/full-search/results-list/results-list.js +25 -0
  26. package/out/components/header/components/header/header.js +105 -0
  27. package/out/components/header/{header.module.pcss.js → components/header/header.module.pcss.js} +0 -0
  28. package/out/components/header/components/header/horizontal-menu/horizontal-menu.js +100 -0
  29. package/out/components/header/{horizontal-menu → components/header/horizontal-menu}/horizontal-menu.module.pcss.js +0 -0
  30. package/out/components/header/{index.js → components/header/index.js} +0 -0
  31. package/out/components/header/{logo-large → components/header/logo-large}/kotlin-logo-large.svg.js +0 -0
  32. package/out/components/header/{logo-large → components/header/logo-large}/logo-large.js +11 -17
  33. package/out/components/header/{logo-large → components/header/logo-large}/logo-large.module.pcss.js +0 -0
  34. package/out/components/header/{logo-small → components/header/logo-small}/kotlin-logo-small.svg.js +0 -0
  35. package/out/components/header/components/header/logo-small/logo-small.js +27 -0
  36. package/out/components/header/{logo-small → components/header/logo-small}/logo-small.module.pcss.js +0 -0
  37. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-button/close-icon.svg.js +0 -0
  38. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-button/hamburger-icon.svg.js +0 -0
  39. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-button/menu-button.js +3 -5
  40. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-button/menu-button.module.pcss.js +0 -0
  41. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-popup.js +28 -37
  42. package/out/components/header/{menu-popup → components/header/menu-popup}/menu-popup.module.pcss.js +0 -0
  43. package/out/components/header/{nav-scheme.js → components/header/nav-scheme.js} +0 -0
  44. package/out/components/header/components/header/quick-search/empty/empty.js +16 -0
  45. package/out/components/header/components/header/quick-search/empty/empty.module.pcss.js +4 -0
  46. package/out/components/header/components/header/quick-search/list/list.js +31 -0
  47. package/out/components/header/components/header/quick-search/list/list.module.pcss.js +6 -0
  48. package/out/components/header/components/header/quick-search/loading/loading.js +14 -0
  49. package/out/components/header/components/header/quick-search/loading/loading.module.pcss.js +4 -0
  50. package/out/components/header/components/header/quick-search/quick-search.js +41 -0
  51. package/out/components/header/components/header/quick-search/quick-search.module.pcss.js +4 -0
  52. package/out/components/header/components/header/quick-search/result/result.js +30 -0
  53. package/out/components/header/components/header/quick-search/result/result.module.pcss.js +6 -0
  54. package/out/components/header/components/header/search-box/search-box.js +69 -0
  55. package/out/components/header/components/header/search-box/search-box.module.pcss.js +4 -0
  56. package/out/components/header/{search-button → components/header/search-button}/search-button.js +6 -6
  57. package/out/components/header/{search-button → components/header/search-button}/search-button.module.pcss.js +0 -0
  58. package/out/components/header/{search-button → components/header/search-button}/search.svg.js +0 -0
  59. package/out/components/header/components/header/search-wrapper/init-search.js +60 -0
  60. package/out/components/header/components/header/search-wrapper/search-const.js +13 -0
  61. package/out/components/header/components/header/search-wrapper/search-context.js +18 -0
  62. package/out/components/header/components/header/search-wrapper/search-with-algolia.js +58 -0
  63. package/out/components/header/components/header/search-wrapper/search-wrapper.js +40 -0
  64. package/out/components/header/components/header/search-wrapper/use-search.js +85 -0
  65. package/out/components/header/helper/is-macos.js +5 -0
  66. package/out/components/header/helper/key-codes.js +3 -0
  67. package/out/components/header/index.css +253 -0
  68. package/out/components/popup/popup.js +34 -40
  69. package/out/components/quotes-slider/quites-slider.js +33 -63
  70. package/out/components/top-menu/dropdown-menu/dropdown-menu.js +28 -40
  71. package/out/components/top-menu/horizontal-menu/horizontal-menu.js +13 -17
  72. package/out/components/top-menu/top-menu.js +19 -23
  73. package/out/components/youtube-player/youtube-player.js +24 -31
  74. package/package.json +13 -8
  75. package/out/components/header/header.js +0 -68
  76. package/out/components/header/horizontal-menu/horizontal-menu.js +0 -110
  77. package/out/components/header/logo-small/logo-small.js +0 -33
@@ -0,0 +1,16 @@
1
+ import React__default from 'react';
2
+ import { useTextStyles } from '@rescui/typography';
3
+ import styles from './empty.module.pcss.js';
4
+
5
+ const EmptyResult = ({
6
+ placeholder
7
+ }) => {
8
+ const textCn = useTextStyles();
9
+ return React__default.createElement("div", {
10
+ className: styles.empty
11
+ }, React__default.createElement("div", {
12
+ className: textCn('rs-h4')
13
+ }, placeholder));
14
+ };
15
+
16
+ export { EmptyResult };
@@ -0,0 +1,4 @@
1
+ var styles = {
2
+ "empty": "ktl-empty-module_empty_xh1i-"
3
+ };
4
+ export { styles as default };
@@ -0,0 +1,31 @@
1
+ import React__default, { useMemo } from 'react';
2
+ import { Result } from '../result/result.js';
3
+ import { useTextStyles } from '@rescui/typography';
4
+ import { isMacOs } from '../../../../helper/is-macos.js';
5
+ import styles from './list.module.pcss.js';
6
+
7
+ const ResultsList = ({
8
+ results,
9
+ searchString,
10
+ toggleFullSearch
11
+ }) => {
12
+ const textCn = useTextStyles();
13
+ const controlledSearchString = useMemo(() => searchString.length > 5 ? `${searchString.substring(0, 5)}...` : searchString, [searchString]);
14
+ return React__default.createElement("div", {
15
+ className: styles.results
16
+ }, results.length ? React__default.createElement(React__default.Fragment, null, React__default.createElement("div", {
17
+ className: styles.topBar
18
+ }, React__default.createElement("div", {
19
+ className: textCn('rs-text-3')
20
+ }, "Showing results for \u00AB", controlledSearchString, "\u00BB"), React__default.createElement("button", {
21
+ className: styles.advancedSearch,
22
+ onClick: toggleFullSearch
23
+ }, React__default.createElement("div", {
24
+ className: textCn('rs-text-3')
25
+ }, "Advanced search ", isMacOs() ? '⌘K' : 'Ctrl+K'))), results.map((searchResult, index) => React__default.createElement(Result, {
26
+ key: index,
27
+ hit: searchResult
28
+ }))) : null);
29
+ };
30
+
31
+ export { ResultsList };
@@ -0,0 +1,6 @@
1
+ var styles = {
2
+ "results": "ktl-list-module_results_LlxqY",
3
+ "topBar": "ktl-list-module_topBar_OO0XT",
4
+ "advancedSearch": "ktl-list-module_advancedSearch_XNy88"
5
+ };
6
+ export { styles as default };
@@ -0,0 +1,14 @@
1
+ import React__default from 'react';
2
+ import { LoadingIcon } from '@rescui/icons';
3
+ import styles from './loading.module.pcss.js';
4
+
5
+ const LoadingResults = () => {
6
+ return React__default.createElement("div", {
7
+ className: styles.loading
8
+ }, React__default.createElement(LoadingIcon, {
9
+ theme: 'dark',
10
+ size: 'l'
11
+ }));
12
+ };
13
+
14
+ export { LoadingResults };
@@ -0,0 +1,4 @@
1
+ var styles = {
2
+ "loading": "ktl-loading-module_loading_CtOhW"
3
+ };
4
+ export { styles as default };
@@ -0,0 +1,41 @@
1
+ import React__default from 'react';
2
+ import { ThemeProvider } from '@rescui/ui-contexts';
3
+ import { ResultsList } from './list/list.js';
4
+ import { EmptyResult } from './empty/empty.js';
5
+ import { LoadingResults } from './loading/loading.js';
6
+ import styles from './quick-search.module.pcss.js';
7
+
8
+ const QuickSearch = ({
9
+ results,
10
+ searchString,
11
+ toggleFullSearch,
12
+ placeholder
13
+ }) => {
14
+ if (placeholder) {
15
+ return React__default.createElement(ThemeProvider, {
16
+ theme: 'dark'
17
+ }, React__default.createElement("div", {
18
+ className: styles.wrapper
19
+ }, React__default.createElement(EmptyResult, {
20
+ placeholder: placeholder
21
+ })));
22
+ }
23
+
24
+ if (results.length > 0) {
25
+ return React__default.createElement(ThemeProvider, {
26
+ theme: 'dark'
27
+ }, React__default.createElement("div", {
28
+ className: styles.wrapper
29
+ }, React__default.createElement(ResultsList, {
30
+ results: results,
31
+ searchString: searchString,
32
+ toggleFullSearch: toggleFullSearch
33
+ })));
34
+ }
35
+
36
+ return React__default.createElement("div", {
37
+ className: styles.wrapper
38
+ }, React__default.createElement(LoadingResults, null));
39
+ };
40
+
41
+ export { QuickSearch };
@@ -0,0 +1,4 @@
1
+ var styles = {
2
+ "wrapper": "ktl-quick-search-module_wrapper_kkbQQ"
3
+ };
4
+ export { styles as default };
@@ -0,0 +1,30 @@
1
+ import React__default from 'react';
2
+ import { useTextStyles } from '@rescui/typography';
3
+ import styles from './result.module.pcss.js';
4
+
5
+ const Result = ({
6
+ hit
7
+ }) => {
8
+ const textCn = useTextStyles();
9
+ const hitTitle = hit.title === hit.mainTitle ? hit.highlightedTitle : `${hit.mainTitle}: ${hit.highlightedTitle}`;
10
+ return React__default.createElement("a", {
11
+ className: styles.result,
12
+ href: hit.url
13
+ }, React__default.createElement("div", {
14
+ className: styles.resultTitle
15
+ }, React__default.createElement("div", {
16
+ className: textCn('rs-h4'),
17
+ dangerouslySetInnerHTML: {
18
+ __html: hitTitle
19
+ }
20
+ })), React__default.createElement("div", {
21
+ className: styles.text
22
+ }, React__default.createElement("div", {
23
+ className: textCn('rs-text-3'),
24
+ dangerouslySetInnerHTML: {
25
+ __html: hit.snippet
26
+ }
27
+ })));
28
+ };
29
+
30
+ export { Result };
@@ -0,0 +1,6 @@
1
+ var styles = {
2
+ "result": "ktl-result-module_result_EKhUw",
3
+ "resultTitle": "ktl-result-module_resultTitle_DSpAT",
4
+ "text": "ktl-result-module_text_fWBKG"
5
+ };
6
+ export { styles as default };
@@ -0,0 +1,69 @@
1
+ import React__default, { useState, useCallback, useEffect } from 'react';
2
+ import Input from '@rescui/input';
3
+ import { SearchIcon, CloseIcon, ErrorIcon } from '@rescui/icons';
4
+ import { QuickSearch } from '../quick-search/quick-search.js';
5
+ import { FullSearch } from '../full-search/full-search.js';
6
+ import styles from './search-box.module.pcss.js';
7
+ import { useSearch } from '../search-wrapper/use-search.js';
8
+ import { isMacOs } from '../../../helper/is-macos.js';
9
+ import { ESC_CODE } from '../../../helper/key-codes.js';
10
+
11
+ const SearchBox = ({
12
+ closeHandler,
13
+ isMobile,
14
+ fullSearchActive,
15
+ setFullSearchActive
16
+ }) => {
17
+ const [searchInput, setSearchInput] = useState('');
18
+ const {
19
+ hits,
20
+ placeholder
21
+ } = useSearch(searchInput, 25);
22
+ const escHandler = useCallback(event => {
23
+ if (event.keyCode === ESC_CODE) {
24
+ closeHandler();
25
+ }
26
+ }, [closeHandler]);
27
+ const handleInput = useCallback(e => {
28
+ setSearchInput(e.target.value);
29
+ }, []);
30
+ const handleClear = useCallback(() => {
31
+ setSearchInput('');
32
+ }, []);
33
+ const toggleFullSearch = useCallback(() => {
34
+ setFullSearchActive(!fullSearchActive);
35
+ }, [fullSearchActive]);
36
+ useEffect(() => {
37
+ if (typeof document !== `undefined`) {
38
+ document.addEventListener('keydown', escHandler);
39
+ return () => {
40
+ document.removeEventListener('keydown', escHandler);
41
+ };
42
+ }
43
+ }, [escHandler]);
44
+ const QuickSearchPlaceholder = isMobile ? 'Search' : `${isMacOs() ? '⌘K' : 'Ctrl+K'} for advanced search`;
45
+ return React__default.createElement("div", {
46
+ className: styles.searchBox
47
+ }, React__default.createElement(Input, {
48
+ placeholder: QuickSearchPlaceholder,
49
+ iconType: 'left',
50
+ value: searchInput,
51
+ onChange: handleInput,
52
+ onClear: isMobile ? closeHandler : handleClear,
53
+ icon: React__default.createElement(SearchIcon, null),
54
+ clearIcon: isMobile ? React__default.createElement(CloseIcon, null) : React__default.createElement(ErrorIcon, null),
55
+ autoFocus: true
56
+ }), searchInput && React__default.createElement(QuickSearch, {
57
+ results: hits,
58
+ searchString: searchInput,
59
+ toggleFullSearch: toggleFullSearch,
60
+ placeholder: placeholder
61
+ }), fullSearchActive && React__default.createElement(FullSearch, {
62
+ toggleFullSearch: toggleFullSearch,
63
+ searchString: searchInput,
64
+ results: hits,
65
+ killSearch: closeHandler
66
+ }));
67
+ };
68
+
69
+ export { SearchBox };
@@ -0,0 +1,4 @@
1
+ var styles = {
2
+ "searchBox": "ktl-search-box-module_searchBox_0SgE9"
3
+ };
4
+ export { styles as default };
@@ -1,22 +1,22 @@
1
- import { jsx } from 'react/jsx-runtime';
1
+ import React__default from 'react';
2
2
  import styles from './search-button.module.pcss.js';
3
3
  import classNames from 'classnames';
4
4
  import SvgSearch from './search.svg.js';
5
+ const DATA_TEST_HEADER_SEARCH_BUTTON = 'header-search-button';
5
6
 
6
7
  const SearchButton = ({
7
8
  onClick,
8
9
  isActive
9
10
  }) => {
10
- return jsx("button", Object.assign({
11
+ return React__default.createElement("button", {
11
12
  type: "button",
12
13
  className: classNames(styles.button, {
13
14
  [styles.active]: isActive
14
15
  }),
16
+ "data-test": DATA_TEST_HEADER_SEARCH_BUTTON,
15
17
  "aria-label": "Search",
16
18
  onClick: onClick
17
- }, {
18
- children: jsx(SvgSearch, {}, void 0)
19
- }), void 0);
19
+ }, React__default.createElement(SvgSearch, null));
20
20
  };
21
21
 
22
- export { SearchButton };
22
+ export { DATA_TEST_HEADER_SEARCH_BUTTON, SearchButton };
@@ -0,0 +1,60 @@
1
+ import algoliasearch from 'algoliasearch/lite';
2
+ import { EXACT_MATCH } from './search-const.js';
3
+ import { searchWithAlgolia } from './search-with-algolia.js';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+ const attributesToRetrieve = ['pageTitle', 'url', 'breadcrumbs', 'mainTitle'];
6
+ const attributesToSnippet = ['content:25'];
7
+
8
+ function initSearch({
9
+ searchAlgoliaId,
10
+ searchAlgoliaApiKey,
11
+ searchAlgoliaIndexName
12
+ }) {
13
+ const [searchIndex, setSearchIndex] = useState(null);
14
+ useEffect(() => {
15
+ if (searchAlgoliaId && searchAlgoliaApiKey && searchAlgoliaIndexName) {
16
+ const algoliaClient = algoliasearch(searchAlgoliaId, searchAlgoliaApiKey);
17
+ setSearchIndex(algoliaClient.initIndex(searchAlgoliaIndexName));
18
+ }
19
+ }, []);
20
+ const search = useMemo(() => async (query, matching, maxHits) => {
21
+ try {
22
+ const isExactSearch = matching === EXACT_MATCH.value;
23
+ const searchArgs = {
24
+ attributesToRetrieve,
25
+ attributesToSnippet,
26
+ isExactSearch,
27
+ maxHits,
28
+ query
29
+ };
30
+
31
+ if (!searchIndex) {
32
+ throw new Error('ConfigData is empty');
33
+ }
34
+
35
+ const searchData = await searchWithAlgolia({ ...searchArgs,
36
+ searchIndex
37
+ });
38
+ const {
39
+ hits,
40
+ totalHits
41
+ } = searchData;
42
+ return {
43
+ hits,
44
+ totalHits
45
+ };
46
+ } catch (error) {
47
+ //reportError(error, { component: 'search' });
48
+ return {
49
+ hits: [],
50
+ totalHits: 0
51
+ };
52
+ }
53
+ }, [searchIndex]);
54
+ return {
55
+ searchIndex,
56
+ search
57
+ };
58
+ }
59
+
60
+ export { initSearch };
@@ -0,0 +1,13 @@
1
+ const FULL_SEARCH = 'full';
2
+ const QUICK_SEARCH = 'quick';
3
+ const DEBOUNCE_SEARCH = 500;
4
+ const DEBOUNCE_QUERY_UPDATE = 100;
5
+ const ALL_RESULTS = {
6
+ label: 'All results',
7
+ value: 'All results'
8
+ };
9
+ const EXACT_MATCH = {
10
+ label: 'Exact match',
11
+ value: 'Exact match'
12
+ };
13
+ export { ALL_RESULTS, DEBOUNCE_QUERY_UPDATE, DEBOUNCE_SEARCH, EXACT_MATCH, FULL_SEARCH, QUICK_SEARCH };
@@ -0,0 +1,18 @@
1
+ import React__default from 'react';
2
+ const searchParamsInitValue = {
3
+ query: '',
4
+ type: '',
5
+ setType: () => null,
6
+ search: () => new Promise(() => ({
7
+ hits: [],
8
+ totalHits: 0
9
+ })),
10
+ matching: '',
11
+ matchingOptions: [],
12
+ setMatching: () => null,
13
+ updateQueryString: () => null,
14
+ searchQueryState: '',
15
+ setSearchQueryState: () => null
16
+ };
17
+ const SearchContext = React__default.createContext(searchParamsInitValue);
18
+ export { SearchContext as default };
@@ -0,0 +1,58 @@
1
+ const snippetEllipsisText = '…';
2
+
3
+ const searchWithAlgolia = async ({
4
+ attributesToRetrieve = [],
5
+ attributesToSnippet = [],
6
+ isExactSearch = false,
7
+ maxHits = 0,
8
+ query = '',
9
+ searchIndex
10
+ }) => {
11
+ try {
12
+ if (searchIndex?.indexName) {
13
+ const searchResponse = await searchIndex.search(query, {
14
+ attributesToRetrieve,
15
+ attributesToSnippet,
16
+ clickAnalytics: true,
17
+ hitsPerPage: maxHits,
18
+ query,
19
+ snippetEllipsisText,
20
+ typoTolerance: !isExactSearch
21
+ });
22
+ return {
23
+ hits: mapResponseToHits(searchResponse.hits),
24
+ totalHits: searchResponse.nbHits
25
+ };
26
+ }
27
+
28
+ throw new Error(`Algolia client can't be initialized`);
29
+ } catch (error) {
30
+ return {
31
+ totalHits: 0,
32
+ hits: []
33
+ };
34
+ }
35
+ };
36
+
37
+ function mapResponseToHits(results) {
38
+ return results.map(({
39
+ _highlightResult,
40
+ _snippetResult,
41
+ breadcrumbs,
42
+ mainTitle,
43
+ objectID,
44
+ pageTitle,
45
+ url
46
+ }) => ({
47
+ breadcrumb: breadcrumbs,
48
+ highlightedTitle: _highlightResult.pageTitle.value,
49
+ hitId: objectID,
50
+ mainTitle,
51
+ pageId: _highlightResult.parent.value,
52
+ snippet: _snippetResult.content.value,
53
+ title: pageTitle,
54
+ url
55
+ }));
56
+ }
57
+
58
+ export { searchWithAlgolia };
@@ -0,0 +1,40 @@
1
+ import React__default, { useState } from 'react';
2
+ import { initSearch } from './init-search.js';
3
+ import SearchContext from './search-context.js';
4
+ import { QUICK_SEARCH, ALL_RESULTS, EXACT_MATCH } from './search-const.js';
5
+ const matchingOptions = [{
6
+ label: ALL_RESULTS.label,
7
+ value: ALL_RESULTS.value
8
+ }, {
9
+ label: EXACT_MATCH.label,
10
+ value: EXACT_MATCH.value
11
+ }];
12
+
13
+ const SearchWrapper = ({
14
+ children,
15
+ searchConfig
16
+ }) => {
17
+ const {
18
+ search
19
+ } = initSearch(searchConfig);
20
+ const [type, setType] = useState(QUICK_SEARCH);
21
+ const [matching, setMatching] = useState(matchingOptions[0].value);
22
+ const [searchQueryState, setSearchQueryState] = useState(''); // router
23
+
24
+ return React__default.createElement(SearchContext.Provider, {
25
+ value: {
26
+ query: '',
27
+ type,
28
+ setType,
29
+ search,
30
+ matching,
31
+ matchingOptions,
32
+ setMatching,
33
+ updateQueryString: () => {},
34
+ searchQueryState,
35
+ setSearchQueryState
36
+ }
37
+ }, children);
38
+ };
39
+
40
+ export { SearchWrapper };
@@ -0,0 +1,85 @@
1
+ import { useState, useContext, useEffect } from 'react';
2
+ import SearchContext from './search-context.js';
3
+ import { DEBOUNCE_QUERY_UPDATE, DEBOUNCE_SEARCH, FULL_SEARCH, QUICK_SEARCH } from './search-const.js';
4
+ let updateQueryTimeout = 0;
5
+ let searchTimeout = 0;
6
+
7
+ const useSearch = (inputValue = '', maxHits = 25) => {
8
+ const [hits, setHits] = useState([]);
9
+ const [totalHits, setTotalHits] = useState(0);
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [queryId, setQueryId] = useState('');
12
+ const [placeholder, setPlaceholder] = useState('');
13
+ const {
14
+ matching,
15
+ updateQueryString,
16
+ type,
17
+ search,
18
+ setSearchQueryState
19
+ } = useContext(SearchContext);
20
+ const isFullSearch = type === FULL_SEARCH;
21
+ const isQuickSearch = type === QUICK_SEARCH;
22
+
23
+ const debouncedSearch = () => {
24
+ if (updateQueryTimeout) {
25
+ clearTimeout(updateQueryTimeout);
26
+ }
27
+
28
+ if (searchTimeout) {
29
+ clearTimeout(searchTimeout);
30
+ }
31
+
32
+ updateQueryTimeout = window.setTimeout(() => {
33
+ if (isQuickSearch) {
34
+ setSearchQueryState(inputValue);
35
+ } else if (isFullSearch) {
36
+ updateQueryString(inputValue, type);
37
+ }
38
+ }, DEBOUNCE_QUERY_UPDATE);
39
+ searchTimeout = window.setTimeout(async () => {
40
+ if (inputValue) {
41
+ setIsLoading(true);
42
+
43
+ try {
44
+ const results = await search(inputValue, matching, maxHits);
45
+
46
+ if (results.hits.length === 0) {
47
+ setPlaceholder(`We’re sorry! We could’t find results for «${inputValue}»`);
48
+ } else {
49
+ setPlaceholder('');
50
+ }
51
+
52
+ setHits(results.hits);
53
+ setTotalHits(results.totalHits);
54
+
55
+ if (results.queryId) {
56
+ setQueryId(results.queryId);
57
+ }
58
+ } catch (error) {
59
+ console.error(error);
60
+ } finally {
61
+ setIsLoading(false);
62
+ }
63
+ } else {
64
+ // Clear hits and placeholder when search string is empty.
65
+ // It prevents show previous negative result when searching from scratch
66
+ setPlaceholder('');
67
+ setHits([]);
68
+ }
69
+ }, DEBOUNCE_SEARCH);
70
+ };
71
+
72
+ useEffect(() => {
73
+ debouncedSearch();
74
+ return () => clearTimeout(updateQueryTimeout);
75
+ }, [inputValue, matching]);
76
+ return {
77
+ hits,
78
+ totalHits,
79
+ isLoading,
80
+ queryId,
81
+ placeholder
82
+ };
83
+ };
84
+
85
+ export { useSearch };
@@ -0,0 +1,5 @@
1
+ function isMacOs() {
2
+ return window.navigator.appVersion.indexOf('Mac') !== -1;
3
+ }
4
+
5
+ export { isMacOs };
@@ -0,0 +1,3 @@
1
+ const ESC_CODE = 27;
2
+ const KEY_K_CODE = 75;
3
+ export { ESC_CODE, KEY_K_CODE };