@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
@@ -1,51 +1,49 @@
1
- import * as React from 'react';
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 { useFuseSearch } from '@portal/search';
5
- import { Autocomplete } from '@theme/components/Search/Autocomplete';
6
- import { SearchItem } from '@theme/components/Search/SearchItem';
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 history = usePreloadHistory();
14
- const { query, setQuery, items, isLoading } = useFuseSearch();
12
+ const [isOpen, setIsOpen] = useState(false);
15
13
  const themeSettings = useThemeConfig();
16
- const { translate } = useTranslate();
17
-
18
- // TODO: ask somebody about typings
19
- const navigate = (item: SearchDocument) => history.push(item.url);
20
-
21
- const renderAutocomplete = () => (
22
- <Autocomplete
23
- items={items}
24
- value={query}
25
- change={setQuery}
26
- select={navigate}
27
- placeholder={translate('theme.search.label', 'Search the docs')}
28
- keyShortcuts={themeSettings?.search?.shortcuts ?? ['/']}
29
- renderItem={(item) => <SearchItem key={item.id} item={item} />}
30
- isLoading={isLoading}
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
- return (
25
- <SearchLink to={item.url} tabIndex={0} innerRef={ref} data-component-name="Search/SearchItem">
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
- <Parameters parameters={item.parameters} />
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
- <Path>{item.path?.join(' → ')}</Path>
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
- </SearchLink>
75
+ </>
41
76
  );
42
77
  }
43
78
 
44
79
  const Title = styled.div`
45
- font-weight: var(--font-weight-bold);
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-small);
75
- color: var(--search-item-title-text-color);
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.8em;
28
- height: 2em;
29
- line-height: 2em;
26
+ font-size: 0.75em;
30
27
  right: 1em;
31
- fill: var(--search-input-text-color);
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/Autocomplete';
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-text-color);
19
- color: var(--color-emphasis-900);
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