@redocly/theme 0.11.5 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/I18n/LanguagePicker.d.ts +4 -0
- package/lib/I18n/LanguagePicker.js +111 -0
- package/lib/I18n/index.d.ts +1 -0
- package/lib/I18n/index.js +6 -0
- package/lib/components/Cards/Card.js +3 -1
- package/lib/components/Catalog/Catalog.js +14 -9
- package/lib/components/Catalog/CatalogCard.js +6 -1
- package/lib/components/Catalog/useCatalog.js +4 -1
- package/lib/components/CodeSample/CodeSample.js +11 -3
- package/lib/components/Feedback/Comment.js +12 -4
- package/lib/components/Feedback/Rating.js +8 -2
- package/lib/components/Feedback/Reasons.js +11 -4
- package/lib/components/Feedback/Sentiment.js +12 -4
- package/lib/components/Feedback/Thumbs.js +31 -18
- package/lib/components/Feedback/useReportDialog.js +8 -2
- package/lib/components/Filter/Filter.js +15 -8
- package/lib/components/Footer/FooterColumn.js +4 -2
- package/lib/components/Footer/FooterCopyright.d.ts +1 -2
- package/lib/components/Footer/FooterCopyright.js +6 -1
- package/lib/components/LastUpdated/LastUpdated.js +8 -4
- package/lib/components/Markdown/MarkdownLayout.js +6 -1
- package/lib/components/Menu/MenuGroup.js +3 -1
- package/lib/components/Menu/MenuItem.js +3 -1
- package/lib/components/Navbar/MobileNavbarItem.js +6 -2
- package/lib/components/Navbar/Navbar.d.ts +1 -0
- package/lib/components/Navbar/Navbar.js +6 -1
- package/lib/components/Navbar/NavbarDropdown.js +3 -1
- package/lib/components/Navbar/NavbarItem.js +9 -4
- package/lib/components/PageNavigation/NextButton.js +7 -2
- package/lib/components/PageNavigation/PreviousButton.js +10 -2
- package/lib/components/Profile/LoginLink.js +6 -1
- package/lib/components/Profile/UserProfileMenu.js +10 -4
- package/lib/components/Search/Autocomplete.d.ts +3 -3
- package/lib/components/Search/Autocomplete.js +21 -11
- package/lib/components/Search/ClearIcon.js +1 -1
- package/lib/components/Search/Search.js +6 -4
- package/lib/components/Separator/Separator.js +3 -1
- package/lib/components/Sidebar/DrilldownMenu.js +6 -1
- package/lib/components/Sidebar/DrilldownMenuItem.js +4 -2
- package/lib/components/Sidebar/types.d.ts +2 -0
- package/lib/components/TableOfContent/TableOfContent.js +6 -1
- package/lib/icons/SpinnerIcon/SpinnerIcon.d.ts +8 -0
- package/lib/icons/SpinnerIcon/SpinnerIcon.js +32 -0
- package/lib/icons/SpinnerIcon/index.d.ts +1 -0
- package/lib/icons/SpinnerIcon/index.js +6 -0
- package/lib/icons/index.d.ts +1 -0
- package/lib/icons/index.js +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/layouts/Forbidden.js +8 -2
- package/lib/layouts/NotFound.js +8 -2
- package/lib/mocks/hooks/index.d.ts +15 -1
- package/lib/mocks/hooks/index.js +19 -1
- package/lib/mocks/search.d.ts +1 -0
- package/lib/mocks/search.js +1 -0
- package/lib/mocks/utils.d.ts +5 -0
- package/lib/mocks/utils.js +9 -1
- package/lib/types/portal/index.d.ts +1 -0
- package/lib/types/portal/index.js +1 -0
- package/lib/types/portal/src/shared/types/catalog.d.ts +4 -0
- package/lib/types/portal/src/shared/types/nav.d.ts +7 -0
- package/package.json +1 -1
- package/src/I18n/LanguagePicker.tsx +113 -0
- package/src/I18n/index.ts +1 -0
- package/src/components/Cards/Card.tsx +5 -1
- package/src/components/Catalog/Catalog.tsx +23 -6
- package/src/components/Catalog/CatalogCard.tsx +8 -1
- package/src/components/Catalog/useCatalog.ts +4 -2
- package/src/components/CodeSample/CodeSample.tsx +22 -4
- package/src/components/Feedback/Comment.tsx +25 -4
- package/src/components/Feedback/Rating.tsx +15 -2
- package/src/components/Feedback/Reasons.tsx +23 -5
- package/src/components/Feedback/Sentiment.tsx +25 -4
- package/src/components/Feedback/Thumbs.tsx +61 -46
- package/src/components/Feedback/useReportDialog.ts +11 -2
- package/src/components/Filter/Filter.tsx +17 -9
- package/src/components/Footer/CustomFooter.tsx +1 -1
- package/src/components/Footer/FooterColumn.tsx +5 -3
- package/src/components/Footer/FooterCopyright.tsx +12 -3
- package/src/components/LastUpdated/LastUpdated.tsx +10 -2
- package/src/components/Markdown/MarkdownLayout.tsx +11 -1
- package/src/components/Menu/MenuGroup.tsx +4 -1
- package/src/components/Menu/MenuItem.tsx +3 -1
- package/src/components/Navbar/MobileNavbarItem.tsx +7 -1
- package/src/components/Navbar/Navbar.tsx +8 -0
- package/src/components/Navbar/NavbarDropdown.tsx +3 -1
- package/src/components/Navbar/NavbarItem.tsx +9 -3
- package/src/components/PageNavigation/NextButton.tsx +8 -2
- package/src/components/PageNavigation/PreviousButton.tsx +11 -2
- package/src/components/Profile/LoginLink.tsx +11 -1
- package/src/components/Profile/UserProfileMenu.tsx +13 -3
- package/src/components/Search/Autocomplete.tsx +31 -17
- package/src/components/Search/ClearIcon.tsx +1 -1
- package/src/components/Search/Search.tsx +8 -7
- package/src/components/Separator/Separator.tsx +4 -1
- package/src/components/Sidebar/DrilldownMenu.tsx +8 -1
- package/src/components/Sidebar/DrilldownMenuItem.tsx +7 -2
- package/src/components/Sidebar/types.ts +2 -0
- package/src/components/TableOfContent/TableOfContent.tsx +11 -1
- package/src/icons/SpinnerIcon/SpinnerIcon.tsx +42 -0
- package/src/icons/SpinnerIcon/index.ts +1 -0
- package/src/icons/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/layouts/Forbidden.tsx +18 -3
- package/src/layouts/NotFound.tsx +17 -3
- package/src/mocks/hooks/index.ts +20 -1
- package/src/mocks/search.ts +2 -0
- package/src/mocks/utils.ts +13 -0
- package/src/types/portal/index.ts +1 -0
- package/src/types/portal/src/shared/types/catalog.ts +4 -0
- package/src/types/portal/src/shared/types/i18n.d.ts +3 -0
- package/src/types/portal/src/shared/types/nav.ts +7 -0
|
@@ -3,12 +3,14 @@ import styled from 'styled-components';
|
|
|
3
3
|
import { useLocation } from 'react-router-dom';
|
|
4
4
|
|
|
5
5
|
import { Link } from '@portal/Link';
|
|
6
|
+
import { getPathnameForLocale } from '@portal/utils';
|
|
6
7
|
import { NavbarDropdown, DropdownWrapper } from '@theme/components/Navbar/NavbarDropdown';
|
|
7
8
|
import type {
|
|
8
9
|
ResolvedNavItem,
|
|
9
10
|
ResolvedNavLinkItem,
|
|
10
11
|
ResolvedNavGroupItem,
|
|
11
12
|
} from '@theme/types/portal';
|
|
13
|
+
import { useI18nConfig, useTranslate } from '@portal/hooks';
|
|
12
14
|
import { withPathPrefix } from '@theme/utils';
|
|
13
15
|
|
|
14
16
|
export interface NavbarItemProps {
|
|
@@ -18,11 +20,15 @@ export interface NavbarItemProps {
|
|
|
18
20
|
|
|
19
21
|
export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element | null {
|
|
20
22
|
const { pathname } = useLocation();
|
|
23
|
+
const { translate } = useTranslate();
|
|
24
|
+
const { defaultLocale, currentLocale, locales } = useI18nConfig();
|
|
21
25
|
|
|
22
26
|
if ((navItem as ResolvedNavLinkItem).link) {
|
|
23
27
|
const item = navItem as ResolvedNavLinkItem;
|
|
24
28
|
|
|
25
|
-
const isActive =
|
|
29
|
+
const isActive =
|
|
30
|
+
pathname ===
|
|
31
|
+
withPathPrefix(getPathnameForLocale(item.link, defaultLocale, currentLocale, locales));
|
|
26
32
|
return (
|
|
27
33
|
<NavbarMenuItem
|
|
28
34
|
active={isActive}
|
|
@@ -30,7 +36,7 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
|
|
|
30
36
|
className={className}
|
|
31
37
|
>
|
|
32
38
|
<NavbarLink to={item.link} external={item.external} target={item.target} active={isActive}>
|
|
33
|
-
<NavbarLabel>{item.label}</NavbarLabel>
|
|
39
|
+
<NavbarLabel>{translate(item.labelTranslationKey, item.label)}</NavbarLabel>
|
|
34
40
|
</NavbarLink>
|
|
35
41
|
</NavbarMenuItem>
|
|
36
42
|
);
|
|
@@ -45,7 +51,7 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
|
|
|
45
51
|
data-component-name="Navbar/NavbarItem"
|
|
46
52
|
className={className}
|
|
47
53
|
>
|
|
48
|
-
<NavbarLabel>{item.label}</NavbarLabel>
|
|
54
|
+
<NavbarLabel>{translate(item.labelTranslationKey, item.label)}</NavbarLabel>
|
|
49
55
|
</NavbarMenuItem>
|
|
50
56
|
<NavbarDropdown items={item.items as ResolvedNavLinkItem[]} />
|
|
51
57
|
</NavbarMenuItemWithDropdownWrapper>
|
|
@@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|
|
4
4
|
import { Button } from '@theme/components/Button';
|
|
5
5
|
import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
6
6
|
import type { ResolvedNavItemWithLink } from '@theme/types/portal';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
7
8
|
|
|
8
9
|
interface NextPageType {
|
|
9
10
|
nextPage?: ResolvedNavItemWithLink | null;
|
|
@@ -11,15 +12,19 @@ interface NextPageType {
|
|
|
11
12
|
|
|
12
13
|
export function NextButton({ nextPage }: NextPageType): JSX.Element {
|
|
13
14
|
const { navigation } = useThemeConfig();
|
|
15
|
+
const { translate } = useTranslate();
|
|
14
16
|
|
|
15
17
|
if (!nextPage || navigation?.nextButton?.hide) {
|
|
16
18
|
return <div> </div>;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const
|
|
21
|
+
const nextPageText = nextPage.label || nextPage.routeSlug || '';
|
|
22
|
+
const defaultText = (navigation?.nextButton?.text || 'Next to {label}').replace(
|
|
20
23
|
'{label}',
|
|
21
|
-
|
|
24
|
+
nextPageText,
|
|
22
25
|
);
|
|
26
|
+
const translationKey = 'theme.page.nextButton';
|
|
27
|
+
const text = translate(translationKey, { label: nextPageText, defaultValue: defaultText });
|
|
23
28
|
|
|
24
29
|
return (
|
|
25
30
|
<StyledButton
|
|
@@ -27,6 +32,7 @@ export function NextButton({ nextPage }: NextPageType): JSX.Element {
|
|
|
27
32
|
size="large"
|
|
28
33
|
to={nextPage.link}
|
|
29
34
|
data-component-name="PageNavigation/NextPageLink"
|
|
35
|
+
data-translation-key={translationKey}
|
|
30
36
|
>
|
|
31
37
|
{text}
|
|
32
38
|
</StyledButton>
|
|
@@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|
|
4
4
|
import { Button } from '@theme/components/Button';
|
|
5
5
|
import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
6
6
|
import type { ResolvedNavItemWithLink } from '@theme/types/portal';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
7
8
|
|
|
8
9
|
interface PreviousPageType {
|
|
9
10
|
prevPage?: ResolvedNavItemWithLink | null;
|
|
@@ -11,15 +12,22 @@ interface PreviousPageType {
|
|
|
11
12
|
|
|
12
13
|
export function PreviousButton({ prevPage }: PreviousPageType): JSX.Element {
|
|
13
14
|
const { navigation } = useThemeConfig();
|
|
15
|
+
const { translate } = useTranslate();
|
|
14
16
|
|
|
15
17
|
if (!prevPage || navigation?.previousButton?.hide) {
|
|
16
18
|
return <div> </div>;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const
|
|
21
|
+
const prevPageText = prevPage.label || prevPage.routeSlug || '';
|
|
22
|
+
const defaultText = (navigation?.previousButton?.text || 'Back to {label}').replace(
|
|
20
23
|
'{label}',
|
|
21
|
-
|
|
24
|
+
prevPageText,
|
|
22
25
|
);
|
|
26
|
+
const translationKey = 'theme.page.previousButton';
|
|
27
|
+
const text = translate(translationKey, {
|
|
28
|
+
label: prevPageText,
|
|
29
|
+
defaultValue: defaultText,
|
|
30
|
+
});
|
|
23
31
|
|
|
24
32
|
return (
|
|
25
33
|
<StyledButton
|
|
@@ -27,6 +35,7 @@ export function PreviousButton({ prevPage }: PreviousPageType): JSX.Element {
|
|
|
27
35
|
size="large"
|
|
28
36
|
to={prevPage.link}
|
|
29
37
|
data-component-name="PageNavigation/PreviousPageLink"
|
|
38
|
+
data-translation-key={translationKey}
|
|
30
39
|
>
|
|
31
40
|
{text}
|
|
32
41
|
</StyledButton>
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
5
|
+
import { useTranslate } from '@portal/hooks';
|
|
5
6
|
|
|
6
7
|
export interface LoginLinkProps {
|
|
7
8
|
href: string;
|
|
@@ -9,7 +10,16 @@ export interface LoginLinkProps {
|
|
|
9
10
|
|
|
10
11
|
export function LoginLink({ href }: LoginLinkProps): JSX.Element {
|
|
11
12
|
const { userProfile } = useThemeConfig();
|
|
12
|
-
|
|
13
|
+
const { translate } = useTranslate();
|
|
14
|
+
const translationKeys = {
|
|
15
|
+
login: 'theme.profile.login',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<StyledLink href={href} data-translation-key={translationKeys.login}>
|
|
20
|
+
{translate(translationKeys.login, userProfile?.loginLabel || 'Login')}
|
|
21
|
+
</StyledLink>
|
|
22
|
+
);
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
const StyledLink = styled.a.attrs(() => ({
|
|
@@ -2,7 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import { Link } from '@portal/Link';
|
|
5
|
-
import { useThemeConfig } from '@theme/hooks';
|
|
5
|
+
import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
6
|
+
import { useTranslate } from '@portal/hooks';
|
|
6
7
|
|
|
7
8
|
interface UserProfileMenuProps {
|
|
8
9
|
setIsOpened: (isOpen: boolean) => void;
|
|
@@ -16,13 +17,20 @@ export function UserProfileMenu({
|
|
|
16
17
|
handleLogout,
|
|
17
18
|
}: UserProfileMenuProps): JSX.Element {
|
|
18
19
|
const { userProfile } = useThemeConfig();
|
|
20
|
+
const { translate } = useTranslate();
|
|
21
|
+
const translationKeys = {
|
|
22
|
+
myApps: 'theme.profile.myApps',
|
|
23
|
+
logout: 'theme.profile.logout',
|
|
24
|
+
};
|
|
19
25
|
|
|
20
26
|
return (
|
|
21
27
|
<>
|
|
22
28
|
<StyledUl onClick={() => setIsOpened(false)}>
|
|
23
29
|
{hasDeveloperOnboarding ? (
|
|
24
30
|
<Link to="/apps">
|
|
25
|
-
<StyledLi
|
|
31
|
+
<StyledLi data-translation-key={translationKeys.myApps}>
|
|
32
|
+
{translate(translationKeys.myApps, 'My Apps')}
|
|
33
|
+
</StyledLi>
|
|
26
34
|
</Link>
|
|
27
35
|
) : null}
|
|
28
36
|
|
|
@@ -32,7 +40,9 @@ export function UserProfileMenu({
|
|
|
32
40
|
</Link>
|
|
33
41
|
))}
|
|
34
42
|
|
|
35
|
-
<StyledLi onClick={() => handleLogout()}
|
|
43
|
+
<StyledLi onClick={() => handleLogout()} data-translation-key={translationKeys.logout}>
|
|
44
|
+
{translate(translationKeys.logout, userProfile?.logoutLabel || 'Log out')}
|
|
45
|
+
</StyledLi>
|
|
36
46
|
</StyledUl>
|
|
37
47
|
</>
|
|
38
48
|
);
|
|
@@ -9,17 +9,21 @@ import { FormInput } from '@theme/components/Search/Input';
|
|
|
9
9
|
import { Popover } from '@theme/components/Search/Popover';
|
|
10
10
|
import { ShortcutKey } from '@theme/components/Search/ShortcutKey';
|
|
11
11
|
import type { ActiveItem } from '@theme/types/portal/src/shared/types/activeItem';
|
|
12
|
+
import { useTranslate } from '@portal/hooks';
|
|
13
|
+
import { SearchIcon } from '@theme/components/Search/SearchIcon';
|
|
14
|
+
import { SpinnerIcon } from '@theme/icons/SpinnerIcon';
|
|
15
|
+
import { ClearIcon } from '@theme/components/Search/ClearIcon';
|
|
12
16
|
|
|
13
17
|
interface AutocompleteProps<T> {
|
|
14
18
|
placeholder?: string;
|
|
15
19
|
value: string;
|
|
16
|
-
items: T[];
|
|
20
|
+
items: T[] | null;
|
|
17
21
|
renderItem(item: ActiveItem<T>): ReactNode;
|
|
18
22
|
change(value: string): void;
|
|
19
23
|
select(item: T): void;
|
|
20
|
-
children?(isOpen: boolean, close: () => void): ReactNode;
|
|
21
24
|
inputRef?: React.RefObject<HTMLInputElement>;
|
|
22
25
|
keyShortcuts?: string[];
|
|
26
|
+
isLoading: boolean;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export function Autocomplete<T>({
|
|
@@ -29,13 +33,17 @@ export function Autocomplete<T>({
|
|
|
29
33
|
change,
|
|
30
34
|
select,
|
|
31
35
|
renderItem,
|
|
32
|
-
children,
|
|
33
36
|
keyShortcuts,
|
|
37
|
+
isLoading,
|
|
34
38
|
}: AutocompleteProps<T>): JSX.Element {
|
|
35
39
|
const location = useLocation();
|
|
36
|
-
const [
|
|
40
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
37
41
|
const [activeIdx, setActiveIdx] = useState(-1);
|
|
38
42
|
const refInput = useRef<HTMLInputElement>(null);
|
|
43
|
+
const { translate } = useTranslate();
|
|
44
|
+
const translationKeys = {
|
|
45
|
+
noResults: 'theme.search.noResults',
|
|
46
|
+
};
|
|
39
47
|
|
|
40
48
|
const hotkeysKeys = keyShortcuts?.join(',');
|
|
41
49
|
|
|
@@ -50,10 +58,10 @@ export function Autocomplete<T>({
|
|
|
50
58
|
}
|
|
51
59
|
}, [hotkeysKeys]);
|
|
52
60
|
|
|
53
|
-
const
|
|
61
|
+
const onFocus = () => setIsFocused(true);
|
|
54
62
|
|
|
55
63
|
const close = () => {
|
|
56
|
-
|
|
64
|
+
setIsFocused(false);
|
|
57
65
|
setActiveIdx(-1);
|
|
58
66
|
};
|
|
59
67
|
|
|
@@ -66,11 +74,12 @@ export function Autocomplete<T>({
|
|
|
66
74
|
|
|
67
75
|
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
68
76
|
setActiveIdx(-1);
|
|
69
|
-
|
|
77
|
+
setIsFocused(true);
|
|
70
78
|
change(event.target.value);
|
|
71
79
|
};
|
|
72
80
|
|
|
73
81
|
const onKeydown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
82
|
+
if (items === null) return;
|
|
74
83
|
switch (event.code) {
|
|
75
84
|
case 'Escape':
|
|
76
85
|
close();
|
|
@@ -101,24 +110,29 @@ export function Autocomplete<T>({
|
|
|
101
110
|
|
|
102
111
|
return (
|
|
103
112
|
<Wrapper data-component-name="Search/Autocomplete">
|
|
104
|
-
{
|
|
113
|
+
{isFocused ? <Overlay onClick={close} /> : null}
|
|
105
114
|
<AutocompleteBox onKeyDown={onKeydown}>
|
|
106
|
-
{
|
|
107
|
-
|
|
115
|
+
{isFocused && isLoading ? <SpinnerIcon /> : <SearchIcon />}
|
|
108
116
|
<FormInput
|
|
109
117
|
value={value}
|
|
110
118
|
placeholder={placeholder}
|
|
111
119
|
onChange={onChange}
|
|
112
|
-
onFocus={
|
|
120
|
+
onFocus={onFocus}
|
|
113
121
|
onClick={stopPropagation}
|
|
114
122
|
ref={refInput}
|
|
115
123
|
/>
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
{isFocused ? <ClearIcon onClick={reset} /> : <ShortcutKey keyShortcuts={keyShortcuts} />}
|
|
125
|
+
{isFocused && items !== null && value && (
|
|
126
|
+
<Popover>
|
|
127
|
+
{items.length ? (
|
|
128
|
+
items.map(mapItem)
|
|
129
|
+
) : (
|
|
130
|
+
<Message data-translation-key={translationKeys.noResults}>
|
|
131
|
+
{translate(translationKeys.noResults, 'No results')}
|
|
132
|
+
</Message>
|
|
133
|
+
)}
|
|
134
|
+
</Popover>
|
|
135
|
+
)}
|
|
122
136
|
</AutocompleteBox>
|
|
123
137
|
</Wrapper>
|
|
124
138
|
);
|
|
@@ -3,17 +3,17 @@ import styled from 'styled-components';
|
|
|
3
3
|
|
|
4
4
|
import { useFuseSearch } from '@portal/search';
|
|
5
5
|
import { Autocomplete } from '@theme/components/Search/Autocomplete';
|
|
6
|
-
import { ClearIcon } from '@theme/components/Search/ClearIcon';
|
|
7
|
-
import { SearchIcon } from '@theme/components/Search/SearchIcon';
|
|
8
6
|
import { SearchItem } from '@theme/components/Search/SearchItem';
|
|
9
7
|
import { useThemeConfig } from '@theme/hooks';
|
|
10
8
|
import type { SearchDocument } from '@theme/types/portal/src/shared/types/searchDocument';
|
|
11
9
|
import { usePreloadHistory } from '@portal/usePreloadHistory';
|
|
10
|
+
import { useTranslate } from '@portal/hooks';
|
|
12
11
|
|
|
13
12
|
export function Search(): JSX.Element {
|
|
14
13
|
const history = usePreloadHistory();
|
|
15
|
-
const { query, setQuery, items } = useFuseSearch();
|
|
14
|
+
const { query, setQuery, items, isLoading } = useFuseSearch();
|
|
16
15
|
const themeSettings = useThemeConfig();
|
|
16
|
+
const { translate } = useTranslate();
|
|
17
17
|
|
|
18
18
|
// TODO: ask somebody about typings
|
|
19
19
|
const navigate = (item: SearchDocument) => history.push(item.url);
|
|
@@ -24,12 +24,11 @@ export function Search(): JSX.Element {
|
|
|
24
24
|
value={query}
|
|
25
25
|
change={setQuery}
|
|
26
26
|
select={navigate}
|
|
27
|
-
placeholder=
|
|
27
|
+
placeholder={translate('theme.search.label', 'Search the docs')}
|
|
28
28
|
keyShortcuts={themeSettings?.search?.shortcuts ?? ['/']}
|
|
29
29
|
renderItem={(item) => <SearchItem key={item.id} item={item} />}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</Autocomplete>
|
|
30
|
+
isLoading={isLoading}
|
|
31
|
+
/>
|
|
33
32
|
);
|
|
34
33
|
|
|
35
34
|
return <Wrapper data-component-name="Search/Search">{renderAutocomplete()}</Wrapper>;
|
|
@@ -38,6 +37,8 @@ export function Search(): JSX.Element {
|
|
|
38
37
|
const Wrapper = styled.div`
|
|
39
38
|
margin-left: auto;
|
|
40
39
|
|
|
40
|
+
padding: 0 var(--navbar-item-padding-horizontal);
|
|
41
|
+
|
|
41
42
|
display: none;
|
|
42
43
|
|
|
43
44
|
${({ theme }) => theme.mediaQueries?.small} {
|
|
@@ -4,11 +4,14 @@ import styled from 'styled-components';
|
|
|
4
4
|
import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
|
|
5
5
|
import { SeparatorItem } from '@theme/components/Separator/SeparatorItem';
|
|
6
6
|
import type { MenuItemProps } from '@theme/components/Sidebar/types';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
7
8
|
|
|
8
9
|
export function Separator({ item }: MenuItemProps): JSX.Element {
|
|
10
|
+
const { translate } = useTranslate();
|
|
11
|
+
|
|
9
12
|
return (
|
|
10
13
|
<Wrapper data-component-name="Sidebar/Separator">
|
|
11
|
-
<SeparatorItem>{item.label}</SeparatorItem>
|
|
14
|
+
<SeparatorItem>{translate(item.labelTranslationKey, item.label)}</SeparatorItem>
|
|
12
15
|
{item.separatorLine ? <SeparatorLine /> : null}
|
|
13
16
|
</Wrapper>
|
|
14
17
|
);
|
|
@@ -4,6 +4,7 @@ import styled, { keyframes } from 'styled-components';
|
|
|
4
4
|
import { BackButton } from '@theme/components/Sidebar/BackButton';
|
|
5
5
|
import { DrilldownMenuItem } from '@theme/components/Sidebar/DrilldownMenuItem';
|
|
6
6
|
import type { DrilldownMenuProps } from '@theme/components/Sidebar/types';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
7
8
|
|
|
8
9
|
export function DrilldownMenu({
|
|
9
10
|
item,
|
|
@@ -11,11 +12,17 @@ export function DrilldownMenu({
|
|
|
11
12
|
prevActiveItem,
|
|
12
13
|
children,
|
|
13
14
|
}: React.PropsWithChildren<DrilldownMenuProps>): JSX.Element {
|
|
15
|
+
const { translate } = useTranslate();
|
|
16
|
+
const translationKeys = {
|
|
17
|
+
backLabel: 'theme.sidebar.menu.backLabel',
|
|
18
|
+
};
|
|
14
19
|
return (
|
|
15
20
|
<MenuContainer data-component-name="Sidebar/DrilldownMenu">
|
|
16
21
|
<MenuContent>
|
|
17
22
|
<MenuWrapper>
|
|
18
|
-
<BackButton back={back}
|
|
23
|
+
<BackButton back={back} data-translation-key={translationKeys.backLabel}>
|
|
24
|
+
{prevActiveItem?.label || translate(translationKeys.backLabel, 'Back')}
|
|
25
|
+
</BackButton>
|
|
19
26
|
<DrilldownMenuItem item={item} />
|
|
20
27
|
</MenuWrapper>
|
|
21
28
|
|
|
@@ -2,14 +2,19 @@ import React from 'react';
|
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { MenuItemProps } from '@theme/components/Sidebar/types';
|
|
5
|
+
import { useTranslate } from '@portal/hooks';
|
|
5
6
|
|
|
6
7
|
export function DrilldownMenuItem({ item }: MenuItemProps): JSX.Element {
|
|
8
|
+
const { translate } = useTranslate();
|
|
9
|
+
|
|
7
10
|
return (
|
|
8
11
|
<Container data-component-name="Sidebar/DrilldownMenuItem">
|
|
9
12
|
{item.icon ? <Icon src={item.icon} /> : null}
|
|
10
13
|
<div>
|
|
11
|
-
{item.label}
|
|
12
|
-
{item.sublabel ?
|
|
14
|
+
{translate(item.labelTranslationKey, item.label)}
|
|
15
|
+
{item.sublabel ? (
|
|
16
|
+
<SubLabel>{translate(item.subLabelTranslationKey, item.sublabel)}</SubLabel>
|
|
17
|
+
) : null}
|
|
13
18
|
</div>
|
|
14
19
|
</Container>
|
|
15
20
|
);
|
|
@@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|
|
4
4
|
import { useFullHeight } from '@theme/hooks/useFullHeight';
|
|
5
5
|
import { useActiveHeading } from '@theme/hooks/useActiveHeading';
|
|
6
6
|
import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
7
|
+
import { useTranslate } from '@portal/hooks';
|
|
7
8
|
import type { MdHeading } from '@theme/types/portal';
|
|
8
9
|
import {
|
|
9
10
|
getDisplayedHeadingsIds,
|
|
@@ -31,6 +32,11 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
|
|
|
31
32
|
getDisplayedHeadingsIds(displayedHeadings),
|
|
32
33
|
);
|
|
33
34
|
|
|
35
|
+
const { translate } = useTranslate();
|
|
36
|
+
const translationKeys = {
|
|
37
|
+
header: 'theme.toc.header',
|
|
38
|
+
};
|
|
39
|
+
|
|
34
40
|
if (toc?.hide) {
|
|
35
41
|
return null;
|
|
36
42
|
}
|
|
@@ -39,7 +45,11 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
|
|
|
39
45
|
<>
|
|
40
46
|
<TableOfContentMenu data-component-name="TableOfContent/TableOfContent">
|
|
41
47
|
<TableOfContentItems ref={sidebar}>
|
|
42
|
-
{displayedHeadings.length ?
|
|
48
|
+
{displayedHeadings.length ? (
|
|
49
|
+
<TocHeader data-translation-key={translationKeys.header}>
|
|
50
|
+
{translate(translationKeys.header, toc.header || 'On this page')}
|
|
51
|
+
</TocHeader>
|
|
52
|
+
) : null}
|
|
43
53
|
{displayedHeadings.map((heading: MdHeading | null, idx: number) => {
|
|
44
54
|
if (!heading) {
|
|
45
55
|
return null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export interface SpinnerIconProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export const Icon = ({ className }: SpinnerIconProps) => (
|
|
8
|
+
<svg className={className} viewBox="0 -1 38 40" strokeOpacity=".5">
|
|
9
|
+
<g fill="none" fillRule="evenodd">
|
|
10
|
+
<g transform="translate(1 1)" strokeWidth="4">
|
|
11
|
+
<circle strokeOpacity=".3" cx="18" cy="18" r="18" />
|
|
12
|
+
<path d="M36 18c0-9.94-8.06-18-18-18">
|
|
13
|
+
<animateTransform
|
|
14
|
+
attributeName="transform"
|
|
15
|
+
type="rotate"
|
|
16
|
+
from="0 18 18"
|
|
17
|
+
to="360 18 18"
|
|
18
|
+
dur="1s"
|
|
19
|
+
repeatCount="indefinite"
|
|
20
|
+
/>
|
|
21
|
+
</path>
|
|
22
|
+
</g>
|
|
23
|
+
</g>
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const SpinnerIcon = styled(Icon).attrs(() => ({
|
|
28
|
+
'data-component-name': 'icons/AnchorIcon/AnchorIcon',
|
|
29
|
+
}))`
|
|
30
|
+
position: absolute;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
width: 1em;
|
|
33
|
+
height: 1em;
|
|
34
|
+
left: 0.8em;
|
|
35
|
+
stroke: var(--search-input-text-color);
|
|
36
|
+
z-index: -1;
|
|
37
|
+
|
|
38
|
+
${({ theme }) => theme.mediaQueries.medium} {
|
|
39
|
+
width: 1.2em;
|
|
40
|
+
height: 1.2em;
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SpinnerIcon } from '@theme/icons/SpinnerIcon/SpinnerIcon';
|
package/src/icons/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,13 +2,28 @@ import React from 'react';
|
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
import { Button } from '@theme';
|
|
4
4
|
|
|
5
|
+
import { useTranslate } from '@portal/hooks';
|
|
6
|
+
|
|
5
7
|
export function Forbidden(): JSX.Element {
|
|
8
|
+
const { translate } = useTranslate();
|
|
9
|
+
const translationKeys = {
|
|
10
|
+
title: 'theme.page.forbidden.title',
|
|
11
|
+
homeButton: 'theme.page.homeButton',
|
|
12
|
+
};
|
|
13
|
+
|
|
6
14
|
return (
|
|
7
15
|
<Wrapper data-component-name="Pages/Forbidden">
|
|
8
16
|
<Header>403</Header>
|
|
9
|
-
<Description
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
<Description data-translation-key={translationKeys.title}>
|
|
18
|
+
{translate(translationKeys.title, 'Access forbidden')}
|
|
19
|
+
</Description>
|
|
20
|
+
<HomeButton
|
|
21
|
+
color="primary"
|
|
22
|
+
size="large"
|
|
23
|
+
to="/"
|
|
24
|
+
data-translation-key={translationKeys.homeButton}
|
|
25
|
+
>
|
|
26
|
+
{translate(translationKeys.homeButton, 'Open Homepage')}
|
|
12
27
|
</HomeButton>
|
|
13
28
|
</Wrapper>
|
|
14
29
|
);
|
package/src/layouts/NotFound.tsx
CHANGED
|
@@ -2,14 +2,28 @@ import React from 'react';
|
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import { Button } from '@theme/components/Button';
|
|
5
|
+
import { useTranslate } from '@portal/hooks';
|
|
5
6
|
|
|
6
7
|
export function NotFound(): JSX.Element {
|
|
8
|
+
const { translate } = useTranslate();
|
|
9
|
+
const translationKeys = {
|
|
10
|
+
title: 'theme.page.notFound.title',
|
|
11
|
+
homeButton: 'theme.page.homeButton',
|
|
12
|
+
};
|
|
13
|
+
|
|
7
14
|
return (
|
|
8
15
|
<Wrapper data-component-name="Pages/NotFound">
|
|
9
16
|
<Header>404</Header>
|
|
10
|
-
<Description
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
<Description data-translation-key={translationKeys.title}>
|
|
18
|
+
{translate(translationKeys.title, `It looks like you're lost`)}
|
|
19
|
+
</Description>
|
|
20
|
+
<HomeButton
|
|
21
|
+
color="primary"
|
|
22
|
+
size="large"
|
|
23
|
+
to="/"
|
|
24
|
+
data-translation-key={translationKeys.homeButton}
|
|
25
|
+
>
|
|
26
|
+
{translate(translationKeys.homeButton, 'Open Homepage')}
|
|
13
27
|
</HomeButton>
|
|
14
28
|
</Wrapper>
|
|
15
29
|
);
|
package/src/mocks/hooks/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThemeUIConfig } from '@theme/config';
|
|
2
|
-
import type { ResolvedNavItem } from '@theme/types/portal';
|
|
2
|
+
import type { ResolvedNavItem, TFunction } from '@theme/types/portal';
|
|
3
3
|
import type { CatalogConfig, FilteredCatalog } from '@theme/types/portal/src/shared/types/catalog';
|
|
4
4
|
interface PageLink {
|
|
5
5
|
label: string;
|
|
@@ -75,4 +75,23 @@ export function useCatalog(_items: ResolvedNavItem[], _config: CatalogConfig): F
|
|
|
75
75
|
throw new Error('Mock not implemented yet.');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function useTranslate() {
|
|
79
|
+
const translate: TFunction = (value?: string, options?: { defaultValue: string } | string) =>
|
|
80
|
+
(typeof options === 'string' ? options : options?.defaultValue) || value || '';
|
|
81
|
+
return { translate };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function useI18n() {
|
|
85
|
+
const changeLanguage = (...args: any) => args.value as string;
|
|
86
|
+
return { changeLanguage };
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
export { useGlobalData } from '../useGlobalData';
|
|
90
|
+
|
|
91
|
+
export function useI18nConfig() {
|
|
92
|
+
return {
|
|
93
|
+
currentLocale: 'en',
|
|
94
|
+
defaultLocale: 'en',
|
|
95
|
+
locales: [{ code: 'en', name: 'en' }],
|
|
96
|
+
};
|
|
97
|
+
}
|
package/src/mocks/search.ts
CHANGED
|
@@ -22,12 +22,14 @@ export function useFuseSearch(): {
|
|
|
22
22
|
query: string;
|
|
23
23
|
setQuery: (val: string) => void;
|
|
24
24
|
items: SearchDocument[];
|
|
25
|
+
isLoading: boolean;
|
|
25
26
|
} {
|
|
26
27
|
const [query, setQuery] = useState('');
|
|
27
28
|
|
|
28
29
|
return {
|
|
29
30
|
query,
|
|
30
31
|
setQuery,
|
|
32
|
+
isLoading: false,
|
|
31
33
|
items: [
|
|
32
34
|
{
|
|
33
35
|
id: '1',
|