@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
|
@@ -1,51 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React, { useEffect, useCallback, useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import hotkeys from 'hotkeys-js';
|
|
3
5
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { SearchTrigger } from '@theme/components/Search/SearchTrigger';
|
|
7
|
+
import { MobileSearchTrigger } from '@theme/components/Search/MobileSearchTrigger';
|
|
8
|
+
import { SearchDialog } from '@theme/components/Search/SearchDialog';
|
|
7
9
|
import { useThemeConfig } from '@theme/hooks';
|
|
8
|
-
import type { SearchDocument } from '@theme/types/portal/src/shared/types/searchDocument';
|
|
9
|
-
import { usePreloadHistory } from '@portal/usePreloadHistory';
|
|
10
|
-
import { useTranslate } from '@portal/hooks';
|
|
11
10
|
|
|
12
11
|
export function Search(): JSX.Element {
|
|
13
|
-
const
|
|
14
|
-
const { query, setQuery, items, isLoading } = useFuseSearch();
|
|
12
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
13
|
const themeSettings = useThemeConfig();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
const location = useLocation();
|
|
15
|
+
const keyShortcuts = themeSettings?.search?.shortcuts ?? ['/'];
|
|
16
|
+
const hotkeysKeys = keyShortcuts?.join(',');
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (hotkeysKeys) {
|
|
20
|
+
hotkeys(hotkeysKeys, (ev) => {
|
|
21
|
+
setIsOpen(true);
|
|
22
|
+
ev.preventDefault();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return () => hotkeys.unbind(hotkeysKeys as string);
|
|
26
|
+
}
|
|
27
|
+
}, [hotkeysKeys]);
|
|
28
|
+
|
|
29
|
+
const onClose = useCallback(() => {
|
|
30
|
+
setIsOpen(false);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
useEffect(onClose, [location, onClose]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Wrapper data-component-name="Search/Search">
|
|
37
|
+
<SearchTrigger onClick={() => setIsOpen(true)} />
|
|
38
|
+
<MobileSearchTrigger onClick={() => setIsOpen(true)} />
|
|
39
|
+
|
|
40
|
+
{isOpen && <SearchDialog onClose={onClose} />}
|
|
41
|
+
</Wrapper>
|
|
32
42
|
);
|
|
33
|
-
|
|
34
|
-
return <Wrapper data-component-name="Search/Search">{renderAutocomplete()}</Wrapper>;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
const Wrapper = styled.div`
|
|
38
46
|
margin-left: auto;
|
|
39
47
|
|
|
40
48
|
padding: 0 var(--navbar-item-padding-horizontal);
|
|
41
|
-
|
|
42
|
-
display: none;
|
|
43
|
-
|
|
44
|
-
${({ theme }) => theme.mediaQueries?.small} {
|
|
45
|
-
display: block;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
${({ theme }) => theme.mediaQueries?.medium} {
|
|
49
|
-
margin: 0 auto;
|
|
50
|
-
}
|
|
51
49
|
`;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { MouseEvent } from 'react';
|
|
5
|
+
|
|
6
|
+
import { useFuseSearch } from '@portal/search';
|
|
7
|
+
import { InputWrapper } from '@theme/components/Search/InputWrapper';
|
|
8
|
+
import { SearchItem } from '@theme/components/Search/SearchItem';
|
|
9
|
+
import { useTranslate } from '@portal/hooks';
|
|
10
|
+
import type { SearchDocument } from '@theme/types/portal/src/shared/types/searchDocument';
|
|
11
|
+
import { Shortcut } from '@theme/components/Search/Shortcut';
|
|
12
|
+
import { RecentSearches } from '@theme/components/Search/RecentSearches';
|
|
13
|
+
import { SuggestedPages } from '@theme/components/Search/SuggestedPages';
|
|
14
|
+
import { CancelSearch } from '@theme/components/Search/CancelSearch';
|
|
15
|
+
import { useDialogHotKeys } from '@theme/hooks/useDialogHotKeys';
|
|
16
|
+
|
|
17
|
+
export const SearchDialog = ({ onClose }: { onClose: () => void }): JSX.Element => {
|
|
18
|
+
const { query, setQuery, items, isLoading } = useFuseSearch();
|
|
19
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
const { translate } = useTranslate();
|
|
21
|
+
|
|
22
|
+
useDialogHotKeys(modalRef, onClose);
|
|
23
|
+
|
|
24
|
+
const handleOverlayClick = (event: MouseEvent<HTMLElement>) => {
|
|
25
|
+
const target = event.target as HTMLElement;
|
|
26
|
+
if (typeof target.className !== 'string') return;
|
|
27
|
+
if (target.className?.includes(' overlay')) {
|
|
28
|
+
onClose();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const translationKeys = {
|
|
33
|
+
noResults: 'theme.search.noResults',
|
|
34
|
+
navigate: 'theme.search.keys.navigate',
|
|
35
|
+
select: 'theme.search.keys.select',
|
|
36
|
+
exit: 'theme.search.keys.exit',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const mapItem = (item: SearchDocument) => {
|
|
40
|
+
return <SearchItem key={item.id} item={item} />;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Overlay
|
|
45
|
+
data-component-name="Search/SearchDialog"
|
|
46
|
+
ref={modalRef}
|
|
47
|
+
onClick={handleOverlayClick}
|
|
48
|
+
className="overlay"
|
|
49
|
+
>
|
|
50
|
+
<Container>
|
|
51
|
+
<TopContainer>
|
|
52
|
+
<InputWrapper
|
|
53
|
+
value={query}
|
|
54
|
+
change={setQuery}
|
|
55
|
+
placeholder={translate('theme.search.label', 'Search docs...')}
|
|
56
|
+
isLoading={isLoading}
|
|
57
|
+
/>
|
|
58
|
+
</TopContainer>
|
|
59
|
+
<Results>
|
|
60
|
+
{items !== null ? (
|
|
61
|
+
items.length ? (
|
|
62
|
+
items.map(mapItem)
|
|
63
|
+
) : (
|
|
64
|
+
<Message data-translation-key={translationKeys.noResults}>
|
|
65
|
+
{translate(translationKeys.noResults, 'No results')}
|
|
66
|
+
</Message>
|
|
67
|
+
)
|
|
68
|
+
) : (
|
|
69
|
+
<>
|
|
70
|
+
<RecentSearches onSelect={setQuery} />
|
|
71
|
+
<SuggestedPages />
|
|
72
|
+
</>
|
|
73
|
+
)}
|
|
74
|
+
</Results>
|
|
75
|
+
<BottomContainer>
|
|
76
|
+
<Shortcuts>
|
|
77
|
+
<Shortcut
|
|
78
|
+
data-translation-key={translationKeys.navigate}
|
|
79
|
+
combination="Tab"
|
|
80
|
+
text={translate(translationKeys.navigate, 'to navigate')}
|
|
81
|
+
/>
|
|
82
|
+
<Shortcut
|
|
83
|
+
data-translation-key={translationKeys.select}
|
|
84
|
+
combination="↵"
|
|
85
|
+
text={translate(translationKeys.select, 'to select')}
|
|
86
|
+
/>
|
|
87
|
+
<Shortcut
|
|
88
|
+
data-translation-key={translationKeys.exit}
|
|
89
|
+
combination="Esc"
|
|
90
|
+
text={translate(translationKeys.exit, 'to exit')}
|
|
91
|
+
/>
|
|
92
|
+
</Shortcuts>
|
|
93
|
+
<CancelSearch onClick={onClose} />
|
|
94
|
+
</BottomContainer>
|
|
95
|
+
</Container>
|
|
96
|
+
</Overlay>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const Overlay = styled.div`
|
|
101
|
+
font-family: var(--font-family-base);
|
|
102
|
+
position: fixed;
|
|
103
|
+
top: 0;
|
|
104
|
+
left: 0;
|
|
105
|
+
width: 100vw;
|
|
106
|
+
height: 100vh;
|
|
107
|
+
background: var(--modal-overlay-background-color);
|
|
108
|
+
z-index: 10000;
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
|
|
113
|
+
& > * {
|
|
114
|
+
background: var(--modal-background-color);
|
|
115
|
+
box-shadow: var(--modal-box-shadow);
|
|
116
|
+
border-radius: 0;
|
|
117
|
+
width: 100vw;
|
|
118
|
+
height: 100vh;
|
|
119
|
+
|
|
120
|
+
${({ theme }) => theme.mediaQueries.small} {
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
width: 600px;
|
|
123
|
+
height: auto;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const Container = styled.div`
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
justify-content: space-between;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
const Results = styled.div`
|
|
135
|
+
flex-grow: 1;
|
|
136
|
+
overflow-y: scroll;
|
|
137
|
+
|
|
138
|
+
${({ theme }) => theme.mediaQueries.small} {
|
|
139
|
+
height: 224px;
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const BottomContainer = styled.footer``;
|
|
144
|
+
|
|
145
|
+
const Shortcuts = styled.div`
|
|
146
|
+
display: none;
|
|
147
|
+
justify-content: flex-end;
|
|
148
|
+
align-items: center;
|
|
149
|
+
padding: 8px 16px 12px;
|
|
150
|
+
gap: 16px;
|
|
151
|
+
|
|
152
|
+
${({ theme }) => theme.mediaQueries?.small} {
|
|
153
|
+
display: flex;
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
const Message = styled.div`
|
|
158
|
+
padding: 24px;
|
|
159
|
+
color: var(--search-item-title-text-color);
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const TopContainer = styled.header``;
|
|
@@ -20,13 +20,11 @@ const Icon = (props: SVGProps<SVGSVGElement>) => (
|
|
|
20
20
|
export const SearchIcon = styled(Icon).attrs(() => ({
|
|
21
21
|
'data-component-name': 'Search/SearchIcon',
|
|
22
22
|
}))`
|
|
23
|
-
position: absolute;
|
|
24
23
|
cursor: pointer;
|
|
25
24
|
width: 1em;
|
|
26
25
|
height: 1em;
|
|
27
26
|
left: 0.8em;
|
|
28
27
|
fill: var(--search-input-text-color);
|
|
29
|
-
z-index: -1;
|
|
30
28
|
|
|
31
29
|
${({ theme }) => theme.mediaQueries.medium} {
|
|
32
30
|
width: 1.2em;
|
|
@@ -3,7 +3,6 @@ import styled from 'styled-components';
|
|
|
3
3
|
|
|
4
4
|
import { OperationBadge } from '@theme/components/OperationBadge';
|
|
5
5
|
import { Link } from '@portal/Link';
|
|
6
|
-
import { Parameters } from '@theme/components/Search/Parameters';
|
|
7
6
|
import { highlight } from '@theme/components/Search/utils';
|
|
8
7
|
import type { ActiveItem } from '@theme/types/portal/src/shared/types/activeItem';
|
|
9
8
|
import type { SearchDocument } from '@theme/types/portal/src/shared/types/searchDocument';
|
|
@@ -21,8 +20,8 @@ export function SearchItem({ item }: SearchItemProps): JSX.Element {
|
|
|
21
20
|
}
|
|
22
21
|
}, [item.active]);
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const header = (
|
|
24
|
+
<>
|
|
26
25
|
{item.httpVerb ? (
|
|
27
26
|
<Operation>
|
|
28
27
|
<OperationBadge type={item.httpVerb}>{item.httpVerb}</OperationBadge>
|
|
@@ -30,20 +29,57 @@ export function SearchItem({ item }: SearchItemProps): JSX.Element {
|
|
|
30
29
|
</Operation>
|
|
31
30
|
) : null}
|
|
32
31
|
<Title>{highlight(item.title)}</Title>
|
|
33
|
-
<Description>{highlight(item.text)}</Description>
|
|
32
|
+
{Array.isArray(item.text) ? <Description>{highlight(item.text)}</Description> : null}
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
34
35
|
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
35
38
|
{item.parameters?.length ? (
|
|
36
|
-
|
|
39
|
+
<>
|
|
40
|
+
{item.parameters.map((param, index) => {
|
|
41
|
+
const path = `${param.place} → ${
|
|
42
|
+
param.path?.length ? param.path?.join(' → ') + ' → ' : ''
|
|
43
|
+
}`;
|
|
44
|
+
return (
|
|
45
|
+
<SearchLink
|
|
46
|
+
key={`${item.id}-${index}`}
|
|
47
|
+
to={item.url}
|
|
48
|
+
tabIndex={0}
|
|
49
|
+
innerRef={ref}
|
|
50
|
+
data-component-name="Search/SearchItem"
|
|
51
|
+
>
|
|
52
|
+
{header}
|
|
53
|
+
<Place>
|
|
54
|
+
<div>
|
|
55
|
+
{path}
|
|
56
|
+
{highlight(param.name)}
|
|
57
|
+
</div>
|
|
58
|
+
<div>{highlight(param.description)}</div>
|
|
59
|
+
</Place>
|
|
60
|
+
</SearchLink>
|
|
61
|
+
);
|
|
62
|
+
})}
|
|
63
|
+
</>
|
|
37
64
|
) : (
|
|
38
|
-
<
|
|
65
|
+
<SearchLink
|
|
66
|
+
to={item.url}
|
|
67
|
+
tabIndex={0}
|
|
68
|
+
innerRef={ref}
|
|
69
|
+
data-component-name="Search/SearchItem"
|
|
70
|
+
>
|
|
71
|
+
{header}
|
|
72
|
+
<Path>{item.path?.join(' → ')}</Path>
|
|
73
|
+
</SearchLink>
|
|
39
74
|
)}
|
|
40
|
-
|
|
75
|
+
</>
|
|
41
76
|
);
|
|
42
77
|
}
|
|
43
78
|
|
|
44
79
|
const Title = styled.div`
|
|
45
|
-
font-weight: var(--font-weight-
|
|
80
|
+
font-weight: var(--font-weight-regular);
|
|
46
81
|
color: var(--search-item-title-text-color);
|
|
82
|
+
padding: 6px 0 2px 0;
|
|
47
83
|
overflow: hidden;
|
|
48
84
|
text-overflow: ellipsis;
|
|
49
85
|
line-height: var(--line-height-base);
|
|
@@ -71,8 +107,8 @@ const SearchLink = styled(Link)`
|
|
|
71
107
|
|
|
72
108
|
const Operation = styled.div`
|
|
73
109
|
font-weight: var(--font-weight-regular);
|
|
74
|
-
font-size: var(--font-size-
|
|
75
|
-
color: var(--search-item-
|
|
110
|
+
font-size: var(--font-size-base);
|
|
111
|
+
color: var(--search-item-path-text-color);
|
|
76
112
|
overflow: hidden;
|
|
77
113
|
text-overflow: ellipsis;
|
|
78
114
|
`;
|
|
@@ -89,3 +125,16 @@ const Path = styled.div`
|
|
|
89
125
|
text-overflow: ellipsis;
|
|
90
126
|
line-height: 22px;
|
|
91
127
|
`;
|
|
128
|
+
|
|
129
|
+
const Place = styled.div`
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
gap: 5px;
|
|
133
|
+
font-size: var(--font-size-small);
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
text-overflow: ellipsis;
|
|
136
|
+
|
|
137
|
+
&:first-child {
|
|
138
|
+
padding-top: 0;
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled, { css } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import { useThemeConfig } from '@theme/hooks';
|
|
5
|
+
import { useTranslate } from '@portal/hooks';
|
|
6
|
+
import { SearchIcon } from '@theme/components/Search/SearchIcon';
|
|
7
|
+
import { ShortcutKey } from '@theme/components/Search/ShortcutKey';
|
|
8
|
+
|
|
9
|
+
export function SearchTrigger({ onClick }: { onClick: () => void }): JSX.Element {
|
|
10
|
+
const themeSettings = useThemeConfig();
|
|
11
|
+
const { translate } = useTranslate();
|
|
12
|
+
const translationKeys = {
|
|
13
|
+
label: 'theme.search.navbar.label',
|
|
14
|
+
};
|
|
15
|
+
const keyShortcuts = themeSettings?.search?.shortcuts ?? ['/'];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Wrapper data-component-name="Search/SearchTrigger" onClick={onClick}>
|
|
19
|
+
<SearchIconSmall />
|
|
20
|
+
<Label data-translation-key={translationKeys.label}>
|
|
21
|
+
{translate(translationKeys.label, 'Search')}
|
|
22
|
+
</Label>
|
|
23
|
+
<ShortcutKey keyShortcuts={keyShortcuts} />
|
|
24
|
+
</Wrapper>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Wrapper = styled.div`
|
|
29
|
+
display: none;
|
|
30
|
+
align-items: center;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
color: var(--search-trigger-text-color);
|
|
33
|
+
background-color: var(--search-trigger-background-color);
|
|
34
|
+
border: var(--search-trigger-border);
|
|
35
|
+
border-radius: var(--search-trigger-border-radius);
|
|
36
|
+
padding: 5px 10px 5px 14px;
|
|
37
|
+
user-select: none;
|
|
38
|
+
|
|
39
|
+
:hover {
|
|
40
|
+
background-color: var(--search-trigger-hover-background-color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
${({ theme }) => {
|
|
44
|
+
return css`
|
|
45
|
+
${theme.mediaQueries.small} {
|
|
46
|
+
display: flex;
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
}}
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const Label = styled.span`
|
|
53
|
+
padding-left: 10px;
|
|
54
|
+
padding-right: 16px;
|
|
55
|
+
font-size: var(--search-trigger-font-size);
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const SearchIconSmall = styled(SearchIcon)`
|
|
59
|
+
width: 13px;
|
|
60
|
+
height: 13px;
|
|
61
|
+
fill: var(--search-trigger-text-color);
|
|
62
|
+
`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export function Shortcut({
|
|
5
|
+
combination,
|
|
6
|
+
text,
|
|
7
|
+
}: {
|
|
8
|
+
combination: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}): JSX.Element {
|
|
11
|
+
return (
|
|
12
|
+
<Wrapper data-component-name="Search/Shortcut">
|
|
13
|
+
<Key>{combination}</Key>
|
|
14
|
+
{text}
|
|
15
|
+
</Wrapper>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Wrapper = styled.div`
|
|
20
|
+
display: flex;
|
|
21
|
+
padding: 2px 4px;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
color: var(--search-item-text-color);
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const Key = styled.span`
|
|
27
|
+
background: rgba(150, 150, 150, 0.1);
|
|
28
|
+
border: 1px solid rgba(100, 100, 100, 0.2);
|
|
29
|
+
border-radius: 3px;
|
|
30
|
+
font-size: 12px;
|
|
31
|
+
padding: 2px 6px;
|
|
32
|
+
`;
|
|
@@ -22,14 +22,9 @@ export function ShortcutKey(props: ShortcutKeyProps): JSX.Element {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const Wrapper = styled.div`
|
|
25
|
-
position: absolute;
|
|
26
25
|
cursor: pointer;
|
|
27
|
-
font-size: 0.
|
|
28
|
-
height: 2em;
|
|
29
|
-
line-height: 2em;
|
|
26
|
+
font-size: 0.75em;
|
|
30
27
|
right: 1em;
|
|
31
|
-
|
|
32
|
-
color: var(--search-input-placeholder-color);
|
|
28
|
+
color: var(--search-trigger-shortcut-text-color);
|
|
33
29
|
opacity: 0.5;
|
|
34
|
-
z-index: -1;
|
|
35
30
|
`;
|
|
@@ -0,0 +1,101 @@
|
|
|
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 { useSuggestedPages } from '@portal/search';
|
|
6
|
+
import { Link } from '@portal/Link';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
8
|
+
|
|
9
|
+
export function SuggestedPages() {
|
|
10
|
+
const pages = useSuggestedPages();
|
|
11
|
+
const { translate } = useTranslate();
|
|
12
|
+
const translationKeys = {
|
|
13
|
+
title: 'theme.search.suggested',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (!pages.length) return null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Wrapper data-component-name="Search/SuggestedPages">
|
|
20
|
+
<Title data-translation-key={translationKeys.title}>
|
|
21
|
+
{translate(translationKeys.title, 'Suggested pages')}
|
|
22
|
+
</Title>
|
|
23
|
+
<SuggestedItems>
|
|
24
|
+
{pages.map(
|
|
25
|
+
(page) =>
|
|
26
|
+
page.link && (
|
|
27
|
+
<Item key={page.label}>
|
|
28
|
+
<PageLink to={page.link} {...page}>
|
|
29
|
+
<Group>
|
|
30
|
+
<ClockBackwardsIcon />
|
|
31
|
+
{page.label}
|
|
32
|
+
</Group>
|
|
33
|
+
</PageLink>
|
|
34
|
+
</Item>
|
|
35
|
+
),
|
|
36
|
+
)}
|
|
37
|
+
</SuggestedItems>
|
|
38
|
+
</Wrapper>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Wrapper = styled.div`
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const Title = styled.div`
|
|
48
|
+
color: var(--search-item-title-text-color);
|
|
49
|
+
font-weight: var(--font-weight-bold);
|
|
50
|
+
font-size: var(--font-size-small);
|
|
51
|
+
line-height: 20px;
|
|
52
|
+
padding: 8px 24px;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const SuggestedItems = styled.div`
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: flex-start;
|
|
59
|
+
order: 1;
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const ClockBackwardsIcon = styled(ThemeClockBackwardsIcon)`
|
|
63
|
+
width: 16px;
|
|
64
|
+
height: 16px;
|
|
65
|
+
fill: #8d8d8d;
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const Item = styled.div`
|
|
69
|
+
display: flex;
|
|
70
|
+
font-size: var(--font-size-base);
|
|
71
|
+
align-items: center;
|
|
72
|
+
padding: 0 12px 4px;
|
|
73
|
+
width: 100%;
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const PageLink = styled(Link)`
|
|
77
|
+
text-decoration: none;
|
|
78
|
+
color: var(--search-item-text-color);
|
|
79
|
+
width: 100%;
|
|
80
|
+
padding: 8px 12px;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
|
|
83
|
+
:hover {
|
|
84
|
+
background-color: var(--search-item-active-background-color);
|
|
85
|
+
color: var(--search-item-active-text-color);
|
|
86
|
+
|
|
87
|
+
${ClockBackwardsIcon} {
|
|
88
|
+
fill: var(--search-item-active-text-color);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
:focus-visible {
|
|
92
|
+
outline: 2px solid var(--color-primary-200);
|
|
93
|
+
border-radius: 2px;
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
const Group = styled.div`
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
`;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
export * from '@theme/components/Search/
|
|
1
|
+
export * from '@theme/components/Search/InputWrapper';
|
|
2
2
|
export * from '@theme/components/Search/ClearIcon';
|
|
3
3
|
export * from '@theme/components/Search/Input';
|
|
4
|
-
export * from '@theme/components/Search/Parameters';
|
|
5
4
|
export * from '@theme/components/Search/Popover';
|
|
6
5
|
export * from '@theme/components/Search/Search';
|
|
7
6
|
export * from '@theme/components/Search/SearchIcon';
|
|
8
7
|
export * from '@theme/components/Search/SearchItem';
|
|
8
|
+
export * from '@theme/components/Search/SearchTrigger';
|
|
9
|
+
export * from '@theme/components/Search/Shortcut';
|
|
10
|
+
export * from '@theme/components/Search/RecentSearches';
|
|
11
|
+
export * from '@theme/components/Search/ClockBackwardsIcon';
|
|
12
|
+
export * from '@theme/components/Search/SuggestedPages';
|
|
13
|
+
export * from '@theme/components/Search/MobileSearchTrigger';
|
|
14
|
+
export * from '@theme/components/Search/CancelSearch';
|
|
9
15
|
export * from '@theme/components/Search/utils';
|
|
@@ -15,6 +15,6 @@ export const highlight = (text: string | string[]): JSX.Element | string => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const Highlight = styled.span`
|
|
18
|
-
background-color: var(--search-highlight-
|
|
19
|
-
color: var(--
|
|
18
|
+
background-color: var(--search-highlight-background-color);
|
|
19
|
+
color: var(--search-highlight-text-color);
|
|
20
20
|
`;
|
|
@@ -4,16 +4,18 @@ import styled from 'styled-components';
|
|
|
4
4
|
type TabProps = {
|
|
5
5
|
activeTab: string;
|
|
6
6
|
label: string;
|
|
7
|
+
className?: string;
|
|
7
8
|
onClick: (e: string) => void;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
export function Tab({ activeTab, label, onClick }: TabProps): JSX.Element {
|
|
11
|
+
export function Tab({ activeTab, label, onClick, className }: TabProps): JSX.Element {
|
|
11
12
|
const isActive = activeTab === label;
|
|
12
13
|
return (
|
|
13
14
|
<TabItem
|
|
14
15
|
active={isActive}
|
|
15
16
|
onClick={() => onClick(label)}
|
|
16
17
|
data-component-name="Markdown/Tabs/Tab"
|
|
18
|
+
className={className}
|
|
17
19
|
>
|
|
18
20
|
{label}
|
|
19
21
|
</TabItem>
|
|
@@ -6,7 +6,7 @@ import type { PropsWithChildren } from 'react';
|
|
|
6
6
|
import { Tab } from '@theme/components/Tabs';
|
|
7
7
|
|
|
8
8
|
type Child = { props: { label: string } & TabsProps };
|
|
9
|
-
type TabsProps = PropsWithChildren<{ children: Child[] }>;
|
|
9
|
+
type TabsProps = PropsWithChildren<{ children: Child[]; className?: string }>;
|
|
10
10
|
|
|
11
11
|
export function Tabs(props: TabsProps): JSX.Element {
|
|
12
12
|
const children = React.Children.toArray(props.children) as Child[];
|
|
@@ -14,7 +14,7 @@ export function Tabs(props: TabsProps): JSX.Element {
|
|
|
14
14
|
const onTabSelect = (label: string) => setActiveTab(label);
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<TabsContainer data-component-name="Markdown/Tabs/Tabs">
|
|
17
|
+
<TabsContainer data-component-name="Markdown/Tabs/Tabs" className={props.className}>
|
|
18
18
|
<TabList>
|
|
19
19
|
{children.map((child) => {
|
|
20
20
|
const { label } = child.props;
|
package/src/config.ts
CHANGED
|
@@ -138,6 +138,16 @@ const navItemsSchema = {
|
|
|
138
138
|
},
|
|
139
139
|
} as const;
|
|
140
140
|
|
|
141
|
+
const suggestedPageSchema = {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
page: { type: 'string' },
|
|
145
|
+
label: { type: 'string' },
|
|
146
|
+
labelTranslationKey: { type: 'string' },
|
|
147
|
+
},
|
|
148
|
+
required: ['page'],
|
|
149
|
+
} as const;
|
|
150
|
+
|
|
141
151
|
export const themeConfigSchema = {
|
|
142
152
|
type: 'object',
|
|
143
153
|
properties: {
|
|
@@ -235,6 +245,10 @@ export const themeConfigSchema = {
|
|
|
235
245
|
items: { type: 'string' },
|
|
236
246
|
default: ['/'],
|
|
237
247
|
},
|
|
248
|
+
suggestedPages: {
|
|
249
|
+
type: 'array',
|
|
250
|
+
items: suggestedPageSchema,
|
|
251
|
+
},
|
|
238
252
|
...hideConfigSchema.properties,
|
|
239
253
|
},
|
|
240
254
|
additionalProperties: false,
|
|
@@ -356,6 +370,7 @@ export type ThemeUIConfig = ThemeConfig & {
|
|
|
356
370
|
};
|
|
357
371
|
search?: {
|
|
358
372
|
shortcuts?: string[];
|
|
373
|
+
suggestedPages?: any[];
|
|
359
374
|
};
|
|
360
375
|
};
|
|
361
376
|
|