@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.
- package/lib/components/Navbar/MobileNavbarMenu.d.ts +2 -3
- package/lib/components/Navbar/MobileNavbarMenu.js +4 -16
- package/lib/components/Navbar/Navbar.js +1 -1
- package/lib/components/Search/CancelSearch.d.ts +4 -0
- package/lib/components/Search/CancelSearch.js +63 -0
- package/lib/components/Search/ClearIcon.js +0 -1
- package/lib/components/Search/ClockBackwardsIcon.d.ts +5 -0
- package/lib/components/Search/ClockBackwardsIcon.js +26 -0
- package/lib/components/Search/Input.js +1 -5
- package/lib/components/Search/InputWrapper.d.ts +10 -0
- package/lib/components/Search/InputWrapper.js +86 -0
- package/lib/components/Search/MobileSearchTrigger.d.ts +4 -0
- package/lib/components/Search/MobileSearchTrigger.js +51 -0
- package/lib/components/Search/RecentSearches.d.ts +4 -0
- package/lib/components/Search/RecentSearches.js +129 -0
- package/lib/components/Search/Search.js +28 -26
- package/lib/components/Search/SearchDialog.d.ts +4 -0
- package/lib/components/Search/SearchDialog.js +136 -0
- package/lib/components/Search/SearchIcon.js +0 -2
- package/lib/components/Search/SearchItem.js +31 -7
- package/lib/components/Search/SearchTrigger.d.ts +4 -0
- package/lib/components/Search/SearchTrigger.js +80 -0
- package/lib/components/Search/Shortcut.d.ts +5 -0
- package/lib/components/Search/Shortcut.js +51 -0
- package/lib/components/Search/ShortcutKey.js +2 -7
- package/lib/components/Search/SuggestedPages.d.ts +2 -0
- package/lib/components/Search/SuggestedPages.js +107 -0
- package/lib/components/Search/index.d.ts +8 -2
- package/lib/components/Search/index.js +8 -2
- package/lib/components/Search/utils.js +2 -2
- package/lib/components/Tabs/Tab.d.ts +2 -1
- package/lib/components/Tabs/Tab.js +2 -2
- package/lib/components/Tabs/Tabs.d.ts +1 -0
- package/lib/components/Tabs/Tabs.js +1 -1
- package/lib/config.d.ts +19 -0
- package/lib/config.js +12 -0
- package/lib/globalStyle.js +22 -11
- package/lib/hooks/useDialogHotKeys.d.ts +2 -0
- package/lib/hooks/useDialogHotKeys.js +45 -0
- package/lib/icons/SpinnerIcon/SpinnerIcon.js +0 -2
- package/lib/mocks/search.d.ts +10 -1
- package/lib/mocks/search.js +19 -1
- package/lib/types/portal/src/shared/types/activeItem.d.ts +1 -1
- package/lib/ui/darkColors.js +6 -1
- package/package.json +3 -3
- package/src/components/Navbar/MobileNavbarMenu.tsx +0 -14
- package/src/components/Navbar/Navbar.tsx +1 -5
- package/src/components/Search/CancelSearch.tsx +42 -0
- package/src/components/Search/ClearIcon.tsx +0 -1
- package/src/components/Search/ClockBackwardsIcon.tsx +26 -0
- package/src/components/Search/Input.tsx +1 -5
- package/src/components/Search/InputWrapper.tsx +93 -0
- package/src/components/Search/MobileSearchTrigger.tsx +27 -0
- package/src/components/Search/RecentSearches.tsx +127 -0
- package/src/components/Search/Search.tsx +35 -37
- package/src/components/Search/SearchDialog.tsx +162 -0
- package/src/components/Search/SearchIcon.tsx +0 -2
- package/src/components/Search/SearchItem.tsx +59 -10
- package/src/components/Search/SearchTrigger.tsx +62 -0
- package/src/components/Search/Shortcut.tsx +32 -0
- package/src/components/Search/ShortcutKey.tsx +2 -7
- package/src/components/Search/SuggestedPages.tsx +101 -0
- package/src/components/Search/index.ts +8 -2
- package/src/components/Search/utils.tsx +2 -2
- package/src/components/Tabs/Tab.tsx +3 -1
- package/src/components/Tabs/Tabs.tsx +2 -2
- package/src/config.ts +15 -0
- package/src/globalStyle.ts +22 -11
- package/src/hooks/useDialogHotKeys.ts +48 -0
- package/src/icons/SpinnerIcon/SpinnerIcon.tsx +0 -2
- package/src/mocks/search.ts +19 -1
- package/src/types/portal/src/shared/types/activeItem.ts +1 -1
- package/src/ui/darkColors.tsx +6 -1
- package/lib/components/Search/Autocomplete.d.ts +0 -16
- package/lib/components/Search/Autocomplete.js +0 -132
- package/lib/components/Search/Parameters.d.ts +0 -7
- package/lib/components/Search/Parameters.js +0 -55
- package/src/components/Search/Autocomplete.tsx +0 -162
- package/src/components/Search/Parameters.tsx +0 -61
package/lib/globalStyle.js
CHANGED
|
@@ -1818,24 +1818,26 @@ const search = (0, styled_components_1.css) `
|
|
|
1818
1818
|
* @tokens Search
|
|
1819
1819
|
*/
|
|
1820
1820
|
|
|
1821
|
-
--search-highlight-
|
|
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:
|
|
1828
|
-
--search-input-text-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:
|
|
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:
|
|
1837
|
+
--search-input-hover-background-color: #fafafa; // @presenter Color
|
|
1837
1838
|
--search-input-hover-border: none; // @presenter Color
|
|
1838
|
-
--search-input-placeholder-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:
|
|
1848
|
-
--search-item-title-text-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
|
|
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:
|
|
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:
|
|
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,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;
|
package/lib/mocks/search.d.ts
CHANGED
|
@@ -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 {};
|
package/lib/mocks/search.js
CHANGED
|
@@ -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
|
package/lib/ui/darkColors.js
CHANGED
|
@@ -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-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
`;
|
|
@@ -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
|
+
`;
|