@redocly/theme 0.13.1 → 0.14.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 (79) hide show
  1. package/lib/components/Navbar/MobileNavbarMenu.d.ts +2 -3
  2. package/lib/components/Navbar/MobileNavbarMenu.js +4 -16
  3. package/lib/components/Navbar/Navbar.js +1 -1
  4. package/lib/components/Search/CancelSearch.d.ts +4 -0
  5. package/lib/components/Search/CancelSearch.js +63 -0
  6. package/lib/components/Search/ClearIcon.js +0 -1
  7. package/lib/components/Search/ClockBackwardsIcon.d.ts +5 -0
  8. package/lib/components/Search/ClockBackwardsIcon.js +26 -0
  9. package/lib/components/Search/Input.js +1 -5
  10. package/lib/components/Search/InputWrapper.d.ts +10 -0
  11. package/lib/components/Search/InputWrapper.js +86 -0
  12. package/lib/components/Search/MobileSearchTrigger.d.ts +4 -0
  13. package/lib/components/Search/MobileSearchTrigger.js +51 -0
  14. package/lib/components/Search/RecentSearches.d.ts +4 -0
  15. package/lib/components/Search/RecentSearches.js +129 -0
  16. package/lib/components/Search/Search.js +28 -26
  17. package/lib/components/Search/SearchDialog.d.ts +4 -0
  18. package/lib/components/Search/SearchDialog.js +136 -0
  19. package/lib/components/Search/SearchIcon.js +0 -2
  20. package/lib/components/Search/SearchItem.js +31 -7
  21. package/lib/components/Search/SearchTrigger.d.ts +4 -0
  22. package/lib/components/Search/SearchTrigger.js +80 -0
  23. package/lib/components/Search/Shortcut.d.ts +5 -0
  24. package/lib/components/Search/Shortcut.js +51 -0
  25. package/lib/components/Search/ShortcutKey.js +2 -7
  26. package/lib/components/Search/SuggestedPages.d.ts +2 -0
  27. package/lib/components/Search/SuggestedPages.js +107 -0
  28. package/lib/components/Search/index.d.ts +8 -2
  29. package/lib/components/Search/index.js +8 -2
  30. package/lib/components/Search/utils.js +2 -2
  31. package/lib/components/Tabs/Tab.d.ts +2 -1
  32. package/lib/components/Tabs/Tab.js +2 -2
  33. package/lib/components/Tabs/Tabs.d.ts +1 -0
  34. package/lib/components/Tabs/Tabs.js +1 -1
  35. package/lib/config.d.ts +19 -0
  36. package/lib/config.js +12 -0
  37. package/lib/globalStyle.js +22 -11
  38. package/lib/hooks/useDialogHotKeys.d.ts +2 -0
  39. package/lib/hooks/useDialogHotKeys.js +45 -0
  40. package/lib/icons/SpinnerIcon/SpinnerIcon.js +0 -2
  41. package/lib/mocks/search.d.ts +10 -1
  42. package/lib/mocks/search.js +19 -1
  43. package/lib/types/portal/src/shared/types/activeItem.d.ts +1 -1
  44. package/lib/ui/darkColors.js +6 -1
  45. package/package.json +3 -3
  46. package/src/components/Navbar/MobileNavbarMenu.tsx +0 -14
  47. package/src/components/Navbar/Navbar.tsx +1 -5
  48. package/src/components/Search/CancelSearch.tsx +42 -0
  49. package/src/components/Search/ClearIcon.tsx +0 -1
  50. package/src/components/Search/ClockBackwardsIcon.tsx +26 -0
  51. package/src/components/Search/Input.tsx +1 -5
  52. package/src/components/Search/InputWrapper.tsx +93 -0
  53. package/src/components/Search/MobileSearchTrigger.tsx +27 -0
  54. package/src/components/Search/RecentSearches.tsx +127 -0
  55. package/src/components/Search/Search.tsx +35 -37
  56. package/src/components/Search/SearchDialog.tsx +162 -0
  57. package/src/components/Search/SearchIcon.tsx +0 -2
  58. package/src/components/Search/SearchItem.tsx +59 -10
  59. package/src/components/Search/SearchTrigger.tsx +62 -0
  60. package/src/components/Search/Shortcut.tsx +32 -0
  61. package/src/components/Search/ShortcutKey.tsx +2 -7
  62. package/src/components/Search/SuggestedPages.tsx +101 -0
  63. package/src/components/Search/index.ts +8 -2
  64. package/src/components/Search/utils.tsx +2 -2
  65. package/src/components/Tabs/Tab.tsx +3 -1
  66. package/src/components/Tabs/Tabs.tsx +2 -2
  67. package/src/config.ts +15 -0
  68. package/src/globalStyle.ts +22 -11
  69. package/src/hooks/useDialogHotKeys.ts +48 -0
  70. package/src/icons/SpinnerIcon/SpinnerIcon.tsx +0 -2
  71. package/src/mocks/search.ts +19 -1
  72. package/src/types/portal/src/shared/types/activeItem.ts +1 -1
  73. package/src/ui/darkColors.tsx +6 -1
  74. package/lib/components/Search/Autocomplete.d.ts +0 -16
  75. package/lib/components/Search/Autocomplete.js +0 -132
  76. package/lib/components/Search/Parameters.d.ts +0 -7
  77. package/lib/components/Search/Parameters.js +0 -55
  78. package/src/components/Search/Autocomplete.tsx +0 -162
  79. package/src/components/Search/Parameters.tsx +0 -61
@@ -1839,24 +1839,26 @@ const search = css`
1839
1839
  * @tokens Search
1840
1840
  */
1841
1841
 
1842
- --search-highlight-text-color: #ffff03; // @presenter Color
1842
+ --search-highlight-background-color: none; // @presenter Color
1843
+ --search-highlight-text-color: #1677FF; // @presenter Color
1843
1844
 
1844
1845
  /**
1845
1846
  * @tokens Portal Search
1846
1847
  */
1847
1848
 
1848
- --search-input-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1849
- --search-input-text-color: #fff; // @presenter Color
1849
+ --search-input-background-color: #fafafa; // @presenter Color
1850
+ --search-input-text-color: var(--text-color); // @presenter Color
1850
1851
 
1851
1852
  --search-input-border: none;
1852
1853
  --search-input-border-radius: var(--border-radius); // @presenter BorderRadius
1853
- --search-input-font-size: var(--navbar-item-font-size); // @presenter FontSize
1854
+ --search-input-font-size: 14px; // @presenter FontSize
1854
1855
  --search-input-font-family: var(--font-family-base); // @presenter FontFamily
1855
1856
  --search-input-line-height: 1.15em; // @presenter LineHeight
1856
1857
 
1857
- --search-input-hover-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1858
+ --search-input-hover-background-color: #fafafa; // @presenter Color
1858
1859
  --search-input-hover-border: none; // @presenter Color
1859
- --search-input-placeholder-color: var(--search-input-text-color);
1860
+ --search-input-placeholder-color: #a8a8a8;
1861
+ --search-input-icons-color: #a8a8a8;
1860
1862
 
1861
1863
  --search-popover-background-color: var(--background-color); // @presenter Color
1862
1864
  --search-popover-border-radius: var(--search-input-border-radius); // @presenter BorderRadius
@@ -1865,12 +1867,21 @@ const search = css`
1865
1867
  --search-popover-border: none;
1866
1868
 
1867
1869
  --search-item-padding: 8px 24px;
1868
- --search-item-text-color: var(--text-color-secondary); // @presenter Color
1869
- --search-item-title-text-color: var(--text-color-secondary); // @presenter Color
1870
+ --search-item-text-color: #525252; // @presenter Color
1871
+ --search-item-title-text-color: #161616; // @presenter Color
1872
+ --search-item-path-text-color: #8d8d8d; // @presenter Color
1870
1873
  --search-item-background-color: transparent; // @presenter Color
1871
- --search-item-active-text-color: var(--text-color-secondary); // @presenter Color
1874
+ --search-item-active-text-color: var(--text-color); // @presenter Color
1872
1875
  --search-item-active-title-text-color: var(--text-color); // @presenter Color
1873
- --search-item-active-background-color: rgba(0, 68, 212, 0.1); // @presenter Color
1876
+ --search-item-active-background-color: #fafafa; // @presenter Color
1877
+
1878
+ --search-trigger-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1879
+ --search-trigger-hover-background-color: var(--navbar-item-hover-background-color); // @presenter Color
1880
+ --search-trigger-border: none; // @presenter Border
1881
+ --search-trigger-border-radius: 6px; // @presenter BorderRadius
1882
+ --search-trigger-text-color: var(--navbar-text-color); // @presenter Color
1883
+ --search-trigger-shortcut-text-color: var(--navbar-text-color); // @presenter Color
1884
+ --search-trigger-font-size: var(--navbar-item-font-size); // @presenter FontSize
1874
1885
 
1875
1886
  /**
1876
1887
  * @tokens Sidebar Search
@@ -2045,7 +2056,7 @@ const modal = css`
2045
2056
  overflow: hidden;
2046
2057
  }
2047
2058
 
2048
- --modal-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.6);
2059
+ --modal-box-shadow: 0px 8px 12px rgba(51, 51, 51, 0.15), 0px 0px 1px rgba(51, 51, 51, 0.31);
2049
2060
  --modal-overlay-background-color: rgba(206, 206, 206, 0.49);
2050
2061
  --modal-background-color: var(--background-color);
2051
2062
  `
@@ -0,0 +1,48 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ import type { MutableRefObject } from 'react';
4
+
5
+ export function useDialogHotKeys(
6
+ ref: MutableRefObject<HTMLElement | null>,
7
+ onClose: () => void,
8
+ ): void {
9
+ const firstFocusableRef = useRef<HTMLElement>();
10
+ const lastFocusableRef = useRef<HTMLElement>();
11
+
12
+ const onKeyDownHandler = (event: KeyboardEvent) => {
13
+ if (event.key === 'Escape') {
14
+ onClose();
15
+ } else if (event.key === 'Tab') {
16
+ if (!event.shiftKey && document.activeElement === lastFocusableRef.current) {
17
+ event.preventDefault();
18
+ firstFocusableRef.current?.focus();
19
+ } else if (event.shiftKey && document.activeElement === firstFocusableRef.current) {
20
+ event.preventDefault();
21
+ lastFocusableRef.current?.focus();
22
+ }
23
+ }
24
+ };
25
+
26
+ const onFocusableElementsHandler = () => {
27
+ const focusableElements = ref.current?.querySelectorAll(
28
+ 'input, [href], [tabindex]:not([tabindex="-1"])',
29
+ );
30
+ if (focusableElements && focusableElements.length > 0) {
31
+ firstFocusableRef.current = focusableElements[0] as HTMLElement;
32
+ lastFocusableRef.current = focusableElements[focusableElements.length - 1] as HTMLElement;
33
+ firstFocusableRef.current?.focus();
34
+ }
35
+ };
36
+
37
+ useEffect(() => {
38
+ document.addEventListener('keydown', onKeyDownHandler);
39
+ return () => {
40
+ document.removeEventListener('keydown', onKeyDownHandler);
41
+ };
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ }, []);
44
+
45
+ useEffect(() => {
46
+ onFocusableElementsHandler();
47
+ });
48
+ }
@@ -27,13 +27,11 @@ export const Icon = ({ className }: SpinnerIconProps) => (
27
27
  export const SpinnerIcon = styled(Icon).attrs(() => ({
28
28
  'data-component-name': 'icons/AnchorIcon/AnchorIcon',
29
29
  }))`
30
- position: absolute;
31
30
  cursor: pointer;
32
31
  width: 1em;
33
32
  height: 1em;
34
33
  left: 0.8em;
35
34
  stroke: var(--search-input-text-color);
36
- z-index: -1;
37
35
 
38
36
  ${({ theme }) => theme.mediaQueries.medium} {
39
37
  width: 1.2em;
@@ -21,7 +21,7 @@ interface SearchDocument {
21
21
  export function useFuseSearch(): {
22
22
  query: string;
23
23
  setQuery: (val: string) => void;
24
- items: SearchDocument[];
24
+ items: SearchDocument[] | null;
25
25
  isLoading: boolean;
26
26
  } {
27
27
  const [query, setQuery] = useState('');
@@ -46,3 +46,21 @@ export function useFuseSearch(): {
46
46
  ],
47
47
  };
48
48
  }
49
+
50
+ export const useRecentSearches = () => {
51
+ const items = ['test'];
52
+ return {
53
+ items,
54
+ addSearchHistoryItem: (value: string) => value,
55
+ removeSearchHistoryItem: (value: string) => value,
56
+ };
57
+ };
58
+
59
+ export const useSuggestedPages = () => {
60
+ return [
61
+ {
62
+ link: '/',
63
+ label: 'some page',
64
+ },
65
+ ];
66
+ };
@@ -1 +1 @@
1
- export type ActiveItem<T> = T & { active: boolean };
1
+ export type ActiveItem<T> = T & { active?: boolean };
@@ -45,9 +45,14 @@ export const darkMode = css`
45
45
  --button-border-color: #0065fb;
46
46
  --button-hover-background-color: #2b4cc4;
47
47
  --button-hover-border-color: #2b4cc4;
48
+ --search-input-background-color: #1b2738;
48
49
  --search-popover-background-color: #1b2738;
49
50
  --search-highlight-text-color: #ffff03;
50
- --search-item-active-background-color: #1f3559;
51
+ --search-item-text-color: #ffffffcf;
52
+ --search-item-title-text-color: #ffffff;
53
+ --search-item-active-text-color: var(--text-color);
54
+ --search-item-active-title-text-color: var(--text-color);
55
+ --search-item-active-background-color: #1b2738;
51
56
  --panel-try-it-nested-body-background-color: #263041;
52
57
  --panel-try-it-tabs-active-background-color: #11151d;
53
58
  --panel-try-it-tabs-hover-background-color: #141a26;
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- import type { ReactNode } from 'react';
3
- import type { ActiveItem } from '../../types/portal/src/shared/types/activeItem';
4
- interface AutocompleteProps<T> {
5
- placeholder?: string;
6
- value: string;
7
- items: T[] | null;
8
- renderItem(item: ActiveItem<T>): ReactNode;
9
- change(value: string): void;
10
- select(item: T): void;
11
- inputRef?: React.RefObject<HTMLInputElement>;
12
- keyShortcuts?: string[];
13
- isLoading: boolean;
14
- }
15
- export declare function Autocomplete<T>({ placeholder, value, items, change, select, renderItem, keyShortcuts, isLoading, }: AutocompleteProps<T>): JSX.Element;
16
- export {};
@@ -1,132 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.Autocomplete = void 0;
30
- const react_1 = __importStar(require("react"));
31
- const react_router_dom_1 = require("react-router-dom");
32
- const styled_components_1 = __importDefault(require("styled-components"));
33
- const hotkeys_js_1 = __importDefault(require("hotkeys-js"));
34
- const Input_1 = require("../../components/Search/Input");
35
- const Popover_1 = require("../../components/Search/Popover");
36
- const ShortcutKey_1 = require("../../components/Search/ShortcutKey");
37
- const hooks_1 = require("../../mocks/hooks");
38
- const SearchIcon_1 = require("../../components/Search/SearchIcon");
39
- const SpinnerIcon_1 = require("../../icons/SpinnerIcon");
40
- const ClearIcon_1 = require("../../components/Search/ClearIcon");
41
- function Autocomplete({ placeholder, value, items, change, select, renderItem, keyShortcuts, isLoading, }) {
42
- const location = (0, react_router_dom_1.useLocation)();
43
- const [isFocused, setIsFocused] = (0, react_1.useState)(false);
44
- const [activeIdx, setActiveIdx] = (0, react_1.useState)(-1);
45
- const refInput = (0, react_1.useRef)(null);
46
- const { translate } = (0, hooks_1.useTranslate)();
47
- const translationKeys = {
48
- noResults: 'theme.search.noResults',
49
- };
50
- const hotkeysKeys = keyShortcuts === null || keyShortcuts === void 0 ? void 0 : keyShortcuts.join(',');
51
- (0, react_1.useEffect)(() => {
52
- if (hotkeysKeys) {
53
- (0, hotkeys_js_1.default)(hotkeysKeys, (ev) => {
54
- var _a;
55
- (_a = refInput.current) === null || _a === void 0 ? void 0 : _a.focus();
56
- ev.preventDefault();
57
- });
58
- return () => hotkeys_js_1.default.unbind(hotkeysKeys);
59
- }
60
- }, [hotkeysKeys]);
61
- const onFocus = () => setIsFocused(true);
62
- const close = () => {
63
- setIsFocused(false);
64
- setActiveIdx(-1);
65
- };
66
- const reset = () => {
67
- change('');
68
- close();
69
- };
70
- const stopPropagation = (event) => event.stopPropagation();
71
- const onChange = (event) => {
72
- setActiveIdx(-1);
73
- setIsFocused(true);
74
- change(event.target.value);
75
- };
76
- const onKeydown = (event) => {
77
- if (items === null)
78
- return;
79
- switch (event.code) {
80
- case 'Escape':
81
- close();
82
- event.currentTarget.blur();
83
- break;
84
- case 'ArrowDown':
85
- setActiveIdx(Math.min(activeIdx + 1, items.length - 1));
86
- event.preventDefault();
87
- break;
88
- case 'ArrowUp':
89
- setActiveIdx(Math.max(0, activeIdx - 1));
90
- break;
91
- case 'Enter':
92
- if (activeIdx > -1) {
93
- reset();
94
- select(items[activeIdx]);
95
- }
96
- break;
97
- }
98
- };
99
- const mapItem = (item, idx) => {
100
- const active = idx === activeIdx;
101
- return renderItem(Object.assign(Object.assign({}, item), { active }));
102
- };
103
- (0, react_1.useEffect)(close, [location]);
104
- return (react_1.default.createElement(Wrapper, { "data-component-name": "Search/Autocomplete" },
105
- isFocused ? react_1.default.createElement(Overlay, { onClick: close }) : null,
106
- react_1.default.createElement(AutocompleteBox, { onKeyDown: onKeydown },
107
- isFocused && isLoading ? react_1.default.createElement(SpinnerIcon_1.SpinnerIcon, null) : react_1.default.createElement(SearchIcon_1.SearchIcon, null),
108
- react_1.default.createElement(Input_1.FormInput, { value: value, placeholder: placeholder, onChange: onChange, onFocus: onFocus, onClick: stopPropagation, ref: refInput }),
109
- isFocused ? react_1.default.createElement(ClearIcon_1.ClearIcon, { onClick: reset }) : react_1.default.createElement(ShortcutKey_1.ShortcutKey, { keyShortcuts: keyShortcuts }),
110
- isFocused && items !== null && value && (react_1.default.createElement(Popover_1.Popover, null, items.length ? (items.map(mapItem)) : (react_1.default.createElement(Message, { "data-translation-key": translationKeys.noResults }, translate(translationKeys.noResults, 'No results'))))))));
111
- }
112
- exports.Autocomplete = Autocomplete;
113
- const Wrapper = styled_components_1.default.div ``;
114
- const AutocompleteBox = styled_components_1.default.div `
115
- display: flex;
116
- align-items: center;
117
- position: relative;
118
- z-index: 1;
119
- `;
120
- const Overlay = styled_components_1.default.div `
121
- position: fixed;
122
- top: 0;
123
- left: 0;
124
- right: 0;
125
- bottom: 0;
126
- z-index: 1;
127
- `;
128
- const Message = styled_components_1.default.div `
129
- padding: 24px;
130
- color: var(--search-item-title-text-color);
131
- `;
132
- //# sourceMappingURL=Autocomplete.js.map
@@ -1,7 +0,0 @@
1
- /// <reference types="react" />
2
- import type { OperationParameter } from '../../types/portal/src/shared/types/searchDocument';
3
- interface ParametersProps {
4
- parameters: OperationParameter[];
5
- }
6
- export declare function Parameters({ parameters }: ParametersProps): JSX.Element;
7
- export {};
@@ -1,55 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Parameters = void 0;
7
- const react_1 = __importDefault(require("react"));
8
- const styled_components_1 = __importDefault(require("styled-components"));
9
- const utils_1 = require("../../components/Search/utils");
10
- const MAX_ITEMS = 2;
11
- function Parameters({ parameters = [] }) {
12
- const moreItems = Math.max(0, parameters.length - MAX_ITEMS);
13
- if (moreItems) {
14
- parameters.length = MAX_ITEMS;
15
- }
16
- return (react_1.default.createElement(Wrapper, { "data-component-name": "Search/Parameters" },
17
- parameters.map((param) => {
18
- var _a, _b;
19
- const path = `${param.place} → ${((_a = param.path) === null || _a === void 0 ? void 0 : _a.length) ? ((_b = param.path) === null || _b === void 0 ? void 0 : _b.join(' → ')) + ' → ' : ''}`;
20
- return (react_1.default.createElement(react_1.default.Fragment, null,
21
- react_1.default.createElement(Place, null,
22
- path,
23
- (0, utils_1.highlight)(param.name),
24
- " ",
25
- react_1.default.createElement("br", null),
26
- (0, utils_1.highlight)(param.description))));
27
- }),
28
- moreItems ? react_1.default.createElement(MoreText, null,
29
- "and ",
30
- moreItems,
31
- " more...") : null));
32
- }
33
- exports.Parameters = Parameters;
34
- const Wrapper = styled_components_1.default.div `
35
- border-left: 1px solid var(--border-color);
36
- padding: 0 12px;
37
- margin-top: 8px;
38
- margin-bottom: 4px;
39
- `;
40
- const Place = styled_components_1.default.div `
41
- font-size: var(--font-size-small);
42
- padding-top: 8px;
43
- overflow: hidden;
44
- text-overflow: ellipsis;
45
-
46
- &:first-child {
47
- padding-top: 0;
48
- }
49
- `;
50
- const MoreText = styled_components_1.default.div `
51
- font-size: var(--font-size-small);
52
- font-weight: var(--font-weight-regular);
53
- padding-top: 4px;
54
- `;
55
- //# sourceMappingURL=Parameters.js.map
@@ -1,162 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { useLocation } from 'react-router-dom';
3
- import styled from 'styled-components';
4
- import hotkeys from 'hotkeys-js';
5
-
6
- import type { ChangeEvent, KeyboardEvent, ReactNode, SyntheticEvent } from 'react';
7
-
8
- import { FormInput } from '@theme/components/Search/Input';
9
- import { Popover } from '@theme/components/Search/Popover';
10
- import { ShortcutKey } from '@theme/components/Search/ShortcutKey';
11
- import type { ActiveItem } from '@theme/types/portal/src/shared/types/activeItem';
12
- import { useTranslate } from '@portal/hooks';
13
- import { SearchIcon } from '@theme/components/Search/SearchIcon';
14
- import { SpinnerIcon } from '@theme/icons/SpinnerIcon';
15
- import { ClearIcon } from '@theme/components/Search/ClearIcon';
16
-
17
- interface AutocompleteProps<T> {
18
- placeholder?: string;
19
- value: string;
20
- items: T[] | null;
21
- renderItem(item: ActiveItem<T>): ReactNode;
22
- change(value: string): void;
23
- select(item: T): void;
24
- inputRef?: React.RefObject<HTMLInputElement>;
25
- keyShortcuts?: string[];
26
- isLoading: boolean;
27
- }
28
-
29
- export function Autocomplete<T>({
30
- placeholder,
31
- value,
32
- items,
33
- change,
34
- select,
35
- renderItem,
36
- keyShortcuts,
37
- isLoading,
38
- }: AutocompleteProps<T>): JSX.Element {
39
- const location = useLocation();
40
- const [isFocused, setIsFocused] = useState(false);
41
- const [activeIdx, setActiveIdx] = useState(-1);
42
- const refInput = useRef<HTMLInputElement>(null);
43
- const { translate } = useTranslate();
44
- const translationKeys = {
45
- noResults: 'theme.search.noResults',
46
- };
47
-
48
- const hotkeysKeys = keyShortcuts?.join(',');
49
-
50
- useEffect(() => {
51
- if (hotkeysKeys) {
52
- hotkeys(hotkeysKeys, (ev) => {
53
- refInput.current?.focus();
54
- ev.preventDefault();
55
- });
56
-
57
- return () => hotkeys.unbind(hotkeysKeys as string);
58
- }
59
- }, [hotkeysKeys]);
60
-
61
- const onFocus = () => setIsFocused(true);
62
-
63
- const close = () => {
64
- setIsFocused(false);
65
- setActiveIdx(-1);
66
- };
67
-
68
- const reset = () => {
69
- change('');
70
- close();
71
- };
72
-
73
- const stopPropagation = (event: SyntheticEvent) => event.stopPropagation();
74
-
75
- const onChange = (event: ChangeEvent<HTMLInputElement>) => {
76
- setActiveIdx(-1);
77
- setIsFocused(true);
78
- change(event.target.value);
79
- };
80
-
81
- const onKeydown = (event: KeyboardEvent<HTMLInputElement>) => {
82
- if (items === null) return;
83
- switch (event.code) {
84
- case 'Escape':
85
- close();
86
- event.currentTarget.blur();
87
- break;
88
- case 'ArrowDown':
89
- setActiveIdx(Math.min(activeIdx + 1, items.length - 1));
90
- event.preventDefault();
91
- break;
92
- case 'ArrowUp':
93
- setActiveIdx(Math.max(0, activeIdx - 1));
94
- break;
95
- case 'Enter':
96
- if (activeIdx > -1) {
97
- reset();
98
- select(items[activeIdx]);
99
- }
100
- break;
101
- }
102
- };
103
-
104
- const mapItem = (item: T, idx: number) => {
105
- const active = idx === activeIdx;
106
- return renderItem({ ...item, active });
107
- };
108
-
109
- useEffect(close, [location]);
110
-
111
- return (
112
- <Wrapper data-component-name="Search/Autocomplete">
113
- {isFocused ? <Overlay onClick={close} /> : null}
114
- <AutocompleteBox onKeyDown={onKeydown}>
115
- {isFocused && isLoading ? <SpinnerIcon /> : <SearchIcon />}
116
- <FormInput
117
- value={value}
118
- placeholder={placeholder}
119
- onChange={onChange}
120
- onFocus={onFocus}
121
- onClick={stopPropagation}
122
- ref={refInput}
123
- />
124
- {isFocused ? <ClearIcon onClick={reset} /> : <ShortcutKey keyShortcuts={keyShortcuts} />}
125
- {isFocused && items !== null && value && (
126
- <Popover>
127
- {items.length ? (
128
- items.map(mapItem)
129
- ) : (
130
- <Message data-translation-key={translationKeys.noResults}>
131
- {translate(translationKeys.noResults, 'No results')}
132
- </Message>
133
- )}
134
- </Popover>
135
- )}
136
- </AutocompleteBox>
137
- </Wrapper>
138
- );
139
- }
140
-
141
- const Wrapper = styled.div``;
142
-
143
- const AutocompleteBox = styled.div`
144
- display: flex;
145
- align-items: center;
146
- position: relative;
147
- z-index: 1;
148
- `;
149
-
150
- const Overlay = styled.div`
151
- position: fixed;
152
- top: 0;
153
- left: 0;
154
- right: 0;
155
- bottom: 0;
156
- z-index: 1;
157
- `;
158
-
159
- const Message = styled.div`
160
- padding: 24px;
161
- color: var(--search-item-title-text-color);
162
- `;
@@ -1,61 +0,0 @@
1
- import React from 'react';
2
- import styled from 'styled-components';
3
-
4
- import { highlight } from '@theme/components/Search/utils';
5
- import type { OperationParameter } from '@theme/types/portal/src/shared/types/searchDocument';
6
- interface ParametersProps {
7
- parameters: OperationParameter[];
8
- }
9
-
10
- const MAX_ITEMS = 2;
11
-
12
- export function Parameters({ parameters = [] }: ParametersProps): JSX.Element {
13
- const moreItems = Math.max(0, parameters.length - MAX_ITEMS);
14
- if (moreItems) {
15
- parameters.length = MAX_ITEMS;
16
- }
17
-
18
- return (
19
- <Wrapper data-component-name="Search/Parameters">
20
- {parameters.map((param) => {
21
- const path = `${param.place} → ${
22
- param.path?.length ? param.path?.join(' → ') + ' → ' : ''
23
- }`;
24
- return (
25
- <>
26
- <Place>
27
- {path}
28
- {highlight(param.name)} <br />
29
- {highlight(param.description)}
30
- </Place>
31
- </>
32
- );
33
- })}
34
- {moreItems ? <MoreText>and {moreItems} more...</MoreText> : null}
35
- </Wrapper>
36
- );
37
- }
38
-
39
- const Wrapper = styled.div`
40
- border-left: 1px solid var(--border-color);
41
- padding: 0 12px;
42
- margin-top: 8px;
43
- margin-bottom: 4px;
44
- `;
45
-
46
- const Place = styled.div`
47
- font-size: var(--font-size-small);
48
- padding-top: 8px;
49
- overflow: hidden;
50
- text-overflow: ellipsis;
51
-
52
- &:first-child {
53
- padding-top: 0;
54
- }
55
- `;
56
-
57
- const MoreText = styled.div`
58
- font-size: var(--font-size-small);
59
- font-weight: var(--font-weight-regular);
60
- padding-top: 4px;
61
- `;