@redocly/theme 0.13.1 → 0.14.1

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
@@ -1818,24 +1818,26 @@ const search = (0, styled_components_1.css) `
1818
1818
  * @tokens Search
1819
1819
  */
1820
1820
 
1821
- --search-highlight-text-color: #ffff03; // @presenter Color
1821
+ --search-highlight-background-color: none; // @presenter Color
1822
+ --search-highlight-text-color: #1677FF; // @presenter Color
1822
1823
 
1823
1824
  /**
1824
1825
  * @tokens Portal Search
1825
1826
  */
1826
1827
 
1827
- --search-input-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1828
- --search-input-text-color: #fff; // @presenter Color
1828
+ --search-input-background-color: #fafafa; // @presenter Color
1829
+ --search-input-text-color: var(--text-color); // @presenter Color
1829
1830
 
1830
1831
  --search-input-border: none;
1831
1832
  --search-input-border-radius: var(--border-radius); // @presenter BorderRadius
1832
- --search-input-font-size: var(--navbar-item-font-size); // @presenter FontSize
1833
+ --search-input-font-size: 14px; // @presenter FontSize
1833
1834
  --search-input-font-family: var(--font-family-base); // @presenter FontFamily
1834
1835
  --search-input-line-height: 1.15em; // @presenter LineHeight
1835
1836
 
1836
- --search-input-hover-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1837
+ --search-input-hover-background-color: #fafafa; // @presenter Color
1837
1838
  --search-input-hover-border: none; // @presenter Color
1838
- --search-input-placeholder-color: var(--search-input-text-color);
1839
+ --search-input-placeholder-color: #a8a8a8;
1840
+ --search-input-icons-color: #a8a8a8;
1839
1841
 
1840
1842
  --search-popover-background-color: var(--background-color); // @presenter Color
1841
1843
  --search-popover-border-radius: var(--search-input-border-radius); // @presenter BorderRadius
@@ -1844,12 +1846,21 @@ const search = (0, styled_components_1.css) `
1844
1846
  --search-popover-border: none;
1845
1847
 
1846
1848
  --search-item-padding: 8px 24px;
1847
- --search-item-text-color: var(--text-color-secondary); // @presenter Color
1848
- --search-item-title-text-color: var(--text-color-secondary); // @presenter Color
1849
+ --search-item-text-color: #525252; // @presenter Color
1850
+ --search-item-title-text-color: #161616; // @presenter Color
1851
+ --search-item-path-text-color: #8d8d8d; // @presenter Color
1849
1852
  --search-item-background-color: transparent; // @presenter Color
1850
- --search-item-active-text-color: var(--text-color-secondary); // @presenter Color
1853
+ --search-item-active-text-color: var(--text-color); // @presenter Color
1851
1854
  --search-item-active-title-text-color: var(--text-color); // @presenter Color
1852
- --search-item-active-background-color: rgba(0, 68, 212, 0.1); // @presenter Color
1855
+ --search-item-active-background-color: #fafafa; // @presenter Color
1856
+
1857
+ --search-trigger-background-color: rgba(255, 255, 255, 0.1); // @presenter Color
1858
+ --search-trigger-hover-background-color: var(--navbar-item-hover-background-color); // @presenter Color
1859
+ --search-trigger-border: none; // @presenter Border
1860
+ --search-trigger-border-radius: 6px; // @presenter BorderRadius
1861
+ --search-trigger-text-color: var(--navbar-text-color); // @presenter Color
1862
+ --search-trigger-shortcut-text-color: var(--navbar-text-color); // @presenter Color
1863
+ --search-trigger-font-size: var(--navbar-item-font-size); // @presenter FontSize
1853
1864
 
1854
1865
  /**
1855
1866
  * @tokens Sidebar Search
@@ -2018,7 +2029,7 @@ const modal = (0, styled_components_1.css) `
2018
2029
  overflow: hidden;
2019
2030
  }
2020
2031
 
2021
- --modal-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.6);
2032
+ --modal-box-shadow: 0px 8px 12px rgba(51, 51, 51, 0.15), 0px 0px 1px rgba(51, 51, 51, 0.31);
2022
2033
  --modal-overlay-background-color: rgba(206, 206, 206, 0.49);
2023
2034
  --modal-background-color: var(--background-color);
2024
2035
  `;
@@ -0,0 +1,2 @@
1
+ import type { MutableRefObject } from 'react';
2
+ export declare function useDialogHotKeys(ref: MutableRefObject<HTMLElement | null>, onClose: () => void): void;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useDialogHotKeys = void 0;
4
+ const react_1 = require("react");
5
+ function useDialogHotKeys(ref, onClose) {
6
+ const firstFocusableRef = (0, react_1.useRef)();
7
+ const lastFocusableRef = (0, react_1.useRef)();
8
+ const onKeyDownHandler = (event) => {
9
+ var _a, _b;
10
+ if (event.key === 'Escape') {
11
+ onClose();
12
+ }
13
+ else if (event.key === 'Tab') {
14
+ if (!event.shiftKey && document.activeElement === lastFocusableRef.current) {
15
+ event.preventDefault();
16
+ (_a = firstFocusableRef.current) === null || _a === void 0 ? void 0 : _a.focus();
17
+ }
18
+ else if (event.shiftKey && document.activeElement === firstFocusableRef.current) {
19
+ event.preventDefault();
20
+ (_b = lastFocusableRef.current) === null || _b === void 0 ? void 0 : _b.focus();
21
+ }
22
+ }
23
+ };
24
+ const onFocusableElementsHandler = () => {
25
+ var _a, _b;
26
+ const focusableElements = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('input, [href], [tabindex]:not([tabindex="-1"])');
27
+ if (focusableElements && focusableElements.length > 0) {
28
+ firstFocusableRef.current = focusableElements[0];
29
+ lastFocusableRef.current = focusableElements[focusableElements.length - 1];
30
+ (_b = firstFocusableRef.current) === null || _b === void 0 ? void 0 : _b.focus();
31
+ }
32
+ };
33
+ (0, react_1.useEffect)(() => {
34
+ document.addEventListener('keydown', onKeyDownHandler);
35
+ return () => {
36
+ document.removeEventListener('keydown', onKeyDownHandler);
37
+ };
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ }, []);
40
+ (0, react_1.useEffect)(() => {
41
+ onFocusableElementsHandler();
42
+ });
43
+ }
44
+ exports.useDialogHotKeys = useDialogHotKeys;
45
+ //# sourceMappingURL=useDialogHotKeys.js.map
@@ -16,13 +16,11 @@ exports.Icon = Icon;
16
16
  exports.SpinnerIcon = (0, styled_components_1.default)(exports.Icon).attrs(() => ({
17
17
  'data-component-name': 'icons/AnchorIcon/AnchorIcon',
18
18
  })) `
19
- position: absolute;
20
19
  cursor: pointer;
21
20
  width: 1em;
22
21
  height: 1em;
23
22
  left: 0.8em;
24
23
  stroke: var(--search-input-text-color);
25
- z-index: -1;
26
24
 
27
25
  ${({ theme }) => theme.mediaQueries.medium} {
28
26
  width: 1.2em;
@@ -17,7 +17,16 @@ interface SearchDocument {
17
17
  export declare function useFuseSearch(): {
18
18
  query: string;
19
19
  setQuery: (val: string) => void;
20
- items: SearchDocument[];
20
+ items: SearchDocument[] | null;
21
21
  isLoading: boolean;
22
22
  };
23
+ export declare const useRecentSearches: () => {
24
+ items: string[];
25
+ addSearchHistoryItem: (value: string) => string;
26
+ removeSearchHistoryItem: (value: string) => string;
27
+ };
28
+ export declare const useSuggestedPages: () => {
29
+ link: string;
30
+ label: string;
31
+ }[];
23
32
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useFuseSearch = void 0;
3
+ exports.useSuggestedPages = exports.useRecentSearches = exports.useFuseSearch = void 0;
4
4
  const react_1 = require("react");
5
5
  function useFuseSearch() {
6
6
  const [query, setQuery] = (0, react_1.useState)('');
@@ -25,4 +25,22 @@ function useFuseSearch() {
25
25
  };
26
26
  }
27
27
  exports.useFuseSearch = useFuseSearch;
28
+ const useRecentSearches = () => {
29
+ const items = ['test'];
30
+ return {
31
+ items,
32
+ addSearchHistoryItem: (value) => value,
33
+ removeSearchHistoryItem: (value) => value,
34
+ };
35
+ };
36
+ exports.useRecentSearches = useRecentSearches;
37
+ const useSuggestedPages = () => {
38
+ return [
39
+ {
40
+ link: '/',
41
+ label: 'some page',
42
+ },
43
+ ];
44
+ };
45
+ exports.useSuggestedPages = useSuggestedPages;
28
46
  //# sourceMappingURL=search.js.map
@@ -1,3 +1,3 @@
1
1
  export type ActiveItem<T> = T & {
2
- active: boolean;
2
+ active?: boolean;
3
3
  };
@@ -47,9 +47,14 @@ exports.darkMode = (0, styled_components_1.css) `
47
47
  --button-border-color: #0065fb;
48
48
  --button-hover-background-color: #2b4cc4;
49
49
  --button-hover-border-color: #2b4cc4;
50
+ --search-input-background-color: #1b2738;
50
51
  --search-popover-background-color: #1b2738;
51
52
  --search-highlight-text-color: #ffff03;
52
- --search-item-active-background-color: #1f3559;
53
+ --search-item-text-color: #ffffffcf;
54
+ --search-item-title-text-color: #ffffff;
55
+ --search-item-active-text-color: var(--text-color);
56
+ --search-item-active-title-text-color: var(--text-color);
57
+ --search-item-active-background-color: #1b2738;
53
58
  --panel-try-it-nested-body-background-color: #263041;
54
59
  --panel-try-it-tabs-active-background-color: #11151d;
55
60
  --panel-try-it-tabs-hover-background-color: #141a26;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "Shared UI components library",
5
5
  "keywords": [],
6
6
  "author": "team@redocly.com",
@@ -20,7 +20,7 @@
20
20
  "prismjs": "^1.28.0",
21
21
  "react": "^17.0.2",
22
22
  "react-dom": "^17.0.2",
23
- "react-router-dom": "^6.4.4",
23
+ "react-router-dom": "^6.10.0",
24
24
  "styled-components": "^5.3.6",
25
25
  "styled-system": "^5.1.5"
26
26
  },
@@ -65,7 +65,7 @@
65
65
  "lodash.throttle": "^4.1.1",
66
66
  "npm-run-all": "^4.1.5",
67
67
  "react-refresh": "^0.14.0",
68
- "react-router-dom": "^6.4.4",
68
+ "react-router-dom": "^6.10.0",
69
69
  "storybook-addon-pseudo-states": "^1.15.1",
70
70
  "storybook-design-token": "^2.9.0",
71
71
  "styled-components": "^5.3.6",
@@ -13,10 +13,8 @@ import type { ResolvedConfigLinks, ResolvedNavItem } from '@theme/types/portal';
13
13
  export function MobileNavbarMenu({
14
14
  menuItems,
15
15
  closeMenu,
16
- search,
17
16
  }: {
18
17
  menuItems: ResolvedConfigLinks;
19
- search: React.ReactNode | undefined;
20
18
  closeMenu: () => void;
21
19
  }): JSX.Element | null {
22
20
  if (isPrimitive(menuItems) || isEmptyArray(menuItems)) {
@@ -26,7 +24,6 @@ export function MobileNavbarMenu({
26
24
  return (
27
25
  <NavbarItemsWrapper data-component-name="Navbar/MobileNavbarMenu">
28
26
  <NavbarItemsContainer>
29
- <MobileSearchWrapper>{search || null}</MobileSearchWrapper>
30
27
  {(menuItems as ResolvedNavItem[]).map((navItem, index) => {
31
28
  return (
32
29
  <MobileNavbarItem
@@ -112,14 +109,3 @@ const NavbarItemsContainer = styled.ul`
112
109
  position: relative;
113
110
  }
114
111
  `;
115
-
116
- const MobileSearchWrapper = styled.div`
117
- padding: var(--navbar-item-padding-horizontal);
118
- > div {
119
- display: block;
120
- width: 100%;
121
- }
122
- input {
123
- width: 100%;
124
- }
125
- `;
@@ -93,11 +93,7 @@ export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Elem
93
93
  {menuItemsExist && <MobileNavbarMenuButton onClick={openMobileMenu} />}
94
94
 
95
95
  {isOpen && (
96
- <MobileNavbarMenu
97
- closeMenu={closeMobileMenu}
98
- menuItems={menu as ResolvedConfigLinks}
99
- search={hideSearch ? null : <Search />}
100
- />
96
+ <MobileNavbarMenu closeMenu={closeMobileMenu} menuItems={menu as ResolvedConfigLinks} />
101
97
  )}
102
98
  <NavbarRow>
103
99
  <NavbarLogo logo={logo} />
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { useTranslate } from '@theme/mocks/hooks';
5
+
6
+ export function CancelSearch({ onClick }: { onClick: () => void }): JSX.Element {
7
+ const { translate } = useTranslate();
8
+ const translationKeys = {
9
+ cancel: 'theme.search.cancel',
10
+ };
11
+
12
+ return (
13
+ <Wrapper data-component-name="Search/CancelSearch" onClick={onClick}>
14
+ <Button data-translation-keys={translationKeys.cancel}>
15
+ {translate(translationKeys.cancel, 'Cancel')}
16
+ </Button>
17
+ </Wrapper>
18
+ );
19
+ }
20
+
21
+ const Button = styled.div`
22
+ display: flex;
23
+ flex-direction: column;
24
+ justify-content: center;
25
+ align-items: center;
26
+ flex-grow: 1;
27
+ padding: 4px 16px;
28
+ border: 1px solid rgba(0, 0, 0, 0.12);
29
+ border-radius: 6px;
30
+ font-size: 14px;
31
+ line-height: 22px;
32
+ height: 32px;
33
+ `;
34
+
35
+ const Wrapper = styled.div`
36
+ display: flex;
37
+ padding: 12px 16px 16px;
38
+
39
+ ${({ theme }) => theme.mediaQueries.small} {
40
+ display: none;
41
+ }
42
+ `;
@@ -20,7 +20,6 @@ const Icon = (props: SVGProps<SVGSVGElement>) => (
20
20
  export const ClearIcon = styled(Icon).attrs(() => ({
21
21
  'data-component-name': 'Search/ClearIcon',
22
22
  }))`
23
- position: absolute;
24
23
  cursor: pointer;
25
24
  width: 0.5em;
26
25
  height: 0.5em;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { SVGProps } from 'react';
5
+
6
+ const Icon = (props: SVGProps<SVGSVGElement>) => (
7
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
8
+ <path d="M10.295 11L7.5 8.205V3.5H8.5V7.79L11 10.295L10.295 11Z" />
9
+ <path d="M8 1C7.06748 1.00013 6.14446 1.18739 5.28562 1.55068C4.42677 1.91397 3.64955 2.44591 3 3.115V1H2V5H6V4H3.54C4.47801 2.95765 5.75149 2.27676 7.13926 2.0756C8.52703 1.87444 9.94142 2.16571 11.1368 2.89882C12.3322 3.63193 13.233 4.76057 13.6829 6.08873C14.1327 7.41689 14.1032 8.86066 13.5993 10.1693C13.0955 11.4779 12.1492 12.5688 10.9249 13.2523C9.7005 13.9359 8.27537 14.1691 6.897 13.9113C5.51862 13.6535 4.27407 12.9211 3.3795 11.8412C2.48492 10.7614 1.99683 9.40227 2 8H1C1 9.38447 1.41054 10.7378 2.17971 11.889C2.94888 13.0401 4.04213 13.9373 5.32122 14.4672C6.6003 14.997 8.00776 15.1356 9.36563 14.8655C10.7235 14.5954 11.9708 13.9287 12.9497 12.9497C13.9287 11.9708 14.5954 10.7235 14.8655 9.36563C15.1356 8.00776 14.997 6.6003 14.4672 5.32122C13.9373 4.04213 13.0401 2.94888 11.889 2.17971C10.7378 1.41054 9.38447 1 8 1Z" />
10
+ </svg>
11
+ );
12
+
13
+ export const ClockBackwardsIcon = styled(Icon).attrs(({ className }: { className?: string }) => ({
14
+ 'data-component-name': 'Search/RecentlyViewedIcon',
15
+ className,
16
+ }))`
17
+ width: 0.5em;
18
+ height: 0.5em;
19
+ right: 1em;
20
+ fill: var(--search-input-text-color);
21
+
22
+ ${({ theme }) => theme.mediaQueries.medium} {
23
+ width: 0.625em;
24
+ height: 0.625em;
25
+ }
26
+ `;
@@ -12,13 +12,9 @@ export const FormInput = styled.input.attrs(() => ({
12
12
  font-size: var(--search-input-font-size);
13
13
  font-family: var(--search-input-font-family);
14
14
  line-height: var(--search-input-line-height);
15
+ width: 100%;
15
16
 
16
17
  ::placeholder {
17
18
  color: var(--search-input-placeholder-color);
18
19
  }
19
-
20
- &:hover {
21
- background-color: var(--search-input-hover-background-color);
22
- border: var(--search-input-hover-border);
23
- }
24
20
  `;
@@ -0,0 +1,93 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { ChangeEvent, SyntheticEvent } from 'react';
5
+
6
+ import { FormInput } from '@theme/components/Search/Input';
7
+ import { SearchIcon as ThemeSearchIcon } from '@theme/components/Search/SearchIcon';
8
+ import { SpinnerIcon as ThemeSpinnerIcon } from '@theme/icons/SpinnerIcon';
9
+ import { ClearIcon as ThemeClearIcon } from '@theme/components/Search/ClearIcon';
10
+
11
+ interface InputWrapperProps {
12
+ placeholder?: string;
13
+ value: string;
14
+ change(value: string): void;
15
+ inputRef?: React.RefObject<HTMLInputElement>;
16
+ isLoading: boolean;
17
+ }
18
+
19
+ export function InputWrapper({
20
+ placeholder,
21
+ value,
22
+ change,
23
+ isLoading,
24
+ }: InputWrapperProps): JSX.Element {
25
+ const [isFocused, setIsFocused] = useState(false);
26
+
27
+ const refInput = useRef<HTMLInputElement>(null);
28
+
29
+ const onFocus = () => setIsFocused(true);
30
+
31
+ const close = () => {
32
+ setIsFocused(false);
33
+ };
34
+
35
+ const reset = () => {
36
+ change('');
37
+ close();
38
+ };
39
+
40
+ const stopPropagation = (event: SyntheticEvent) => event.stopPropagation();
41
+
42
+ const onChange = (event: ChangeEvent<HTMLInputElement>) => {
43
+ setIsFocused(true);
44
+ change(event.target.value);
45
+ };
46
+
47
+ return (
48
+ <Wrapper data-component-name="Search/InputContainer">
49
+ {isFocused && isLoading ? <SpinnerIcon /> : <SearchIcon />}
50
+ <FormInput
51
+ value={value}
52
+ placeholder={placeholder}
53
+ onChange={onChange}
54
+ onFocus={onFocus}
55
+ onClick={stopPropagation}
56
+ ref={refInput}
57
+ />
58
+ {!!value && <ClearIcon onClick={reset} />}
59
+ </Wrapper>
60
+ );
61
+ }
62
+
63
+ const Wrapper = styled.div`
64
+ display: flex;
65
+ align-items: center;
66
+ padding: 12px 16px;
67
+ height: 56px;
68
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
69
+ background-color: var(--search-input-background-color);
70
+ border-radius: 0;
71
+
72
+ ${({ theme }) => theme.mediaQueries.small} {
73
+ border-radius: 8px 8px 0 0;
74
+ }
75
+ `;
76
+
77
+ const SearchIcon = styled(ThemeSearchIcon)`
78
+ width: 20px;
79
+ height: 20px;
80
+ fill: var(--search-input-placeholder-color);
81
+ `;
82
+
83
+ const SpinnerIcon = styled(ThemeSpinnerIcon)`
84
+ width: 20px;
85
+ height: 20px;
86
+ fill: var(--search-input-placeholder-color);
87
+ `;
88
+
89
+ const ClearIcon = styled(ThemeClearIcon)`
90
+ width: 12px;
91
+ height: 12px;
92
+ fill: var(--search-input-placeholder-color);
93
+ `;
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { SearchIcon as ThemeSearchIcon } from '@theme/components/Search/SearchIcon';
5
+
6
+ export function MobileSearchTrigger({ onClick }: { onClick: () => void }): JSX.Element {
7
+ return (
8
+ <Wrapper data-component-name="Search/MobileSearchTrigger" onClick={onClick}>
9
+ <SearchIcon />
10
+ </Wrapper>
11
+ );
12
+ }
13
+
14
+ const SearchIcon = styled(ThemeSearchIcon)`
15
+ width: 20px;
16
+ height: 20px;
17
+ fill: #8d8d8d;
18
+ `;
19
+
20
+ const Wrapper = styled.div`
21
+ display: block;
22
+ user-select: none;
23
+
24
+ ${({ theme }) => theme.mediaQueries.small} {
25
+ display: none;
26
+ }
27
+ `;
@@ -0,0 +1,127 @@
1
+ import * as React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import { ClockBackwardsIcon as ThemeClockBackwardsIcon } from '@theme/components/Search/ClockBackwardsIcon';
5
+ import { ClearIcon as ThemeClearIcon } from '@theme/components/Search/ClearIcon';
6
+ import { useRecentSearches } from '@portal/search';
7
+ import { useTranslate } from '@portal/hooks';
8
+
9
+ export function RecentSearches({ onSelect }: { onSelect: (value: string) => void }) {
10
+ const { items, removeSearchHistoryItem } = useRecentSearches();
11
+ const { translate } = useTranslate();
12
+ const translationKeys = {
13
+ title: 'theme.search.recent',
14
+ };
15
+
16
+ if (!items || !items.length) return null;
17
+
18
+ const removeItem = (e: React.MouseEvent<SVGSVGElement, MouseEvent>, item: string) => {
19
+ e.stopPropagation();
20
+ removeSearchHistoryItem(item);
21
+ };
22
+
23
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, item: string) => {
24
+ if (e.key === 'Enter') {
25
+ onSelect(item);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <Wrapper data-component-name="Search/RecentSearches">
31
+ <Title data-translation-key={translationKeys.title}>
32
+ {translate(translationKeys.title, 'Recent searches')}
33
+ </Title>
34
+ <RecentItems>
35
+ {items.map((item) => (
36
+ <Item
37
+ key={item}
38
+ onClick={() => onSelect(item)}
39
+ onKeyDown={(e) => handleKeyDown(e, item)}
40
+ tabIndex={0}
41
+ role="link"
42
+ >
43
+ <Group>
44
+ <ClockBackwardsIcon />
45
+ {item}
46
+ </Group>
47
+ <ClearIcon onClick={(e) => removeItem(e, item)} />
48
+ </Item>
49
+ ))}
50
+ </RecentItems>
51
+ </Wrapper>
52
+ );
53
+ }
54
+
55
+ const Wrapper = styled.div`
56
+ display: flex;
57
+ flex-direction: column;
58
+ `;
59
+
60
+ const Title = styled.div`
61
+ color: var(--search-item-title-text-color);
62
+ font-weight: 600;
63
+ font-size: var(--font-size-small);
64
+ line-height: 20px;
65
+ padding: 8px 24px;
66
+ `;
67
+
68
+ const RecentItems = styled.div`
69
+ display: flex;
70
+ flex-direction: column;
71
+ align-items: flex-start;
72
+ padding: 0px 12px 12px;
73
+ `;
74
+
75
+ const ClockBackwardsIcon = styled(ThemeClockBackwardsIcon)`
76
+ width: 16px;
77
+ height: 16px;
78
+ fill: #8d8d8d;
79
+ `;
80
+
81
+ const ClearIcon = styled(ThemeClearIcon)`
82
+ display: inline;
83
+ width: 10px;
84
+ height: 10px;
85
+ fill: var(--search-item-active-text-color))
86
+ `;
87
+
88
+ const Item = styled.div`
89
+ display: flex;
90
+ font-size: var(--font-size-base);
91
+ justify-content: space-between;
92
+ align-items: center;
93
+ padding: 8px 12px;
94
+ color: var(--search-item-text-color);
95
+ width: 100%;
96
+ cursor: pointer;
97
+
98
+ :hover {
99
+ background-color: var(--search-item-active-background-color);
100
+ color: var(--search-item-active-text-color);
101
+
102
+ ${ClockBackwardsIcon} {
103
+ fill: var(--search-item-active-text-color);
104
+ }
105
+
106
+ ${ClearIcon} {
107
+ display: inline;
108
+ }
109
+ }
110
+
111
+ :focus-visible {
112
+ outline: 2px solid var(--color-primary-200);
113
+ border-radius: 2px;
114
+ }
115
+
116
+ ${({ theme }) => theme.mediaQueries.small} {
117
+ ${ClearIcon} {
118
+ display: none;
119
+ }
120
+ }
121
+ `;
122
+
123
+ const Group = styled.div`
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 8px;
127
+ `;