@redocly/theme 0.11.4 → 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/Markdown/MarkdownWrapper.js +4 -0
- 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/globalStyle.js +7 -0
- 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/Markdown/MarkdownWrapper.tsx +4 -0
- 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/globalStyle.ts +7 -0
- 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
|
@@ -13,6 +13,9 @@ import { useThemeConfig } from '@theme/hooks/useThemeConfig';
|
|
|
13
13
|
import { Search } from '@theme/components/Search/Search';
|
|
14
14
|
import { AuthUserProfile } from '@theme/components/Profile/AuthUserProfile';
|
|
15
15
|
import type { LogoConfig, ResolvedConfigLinks } from '@theme/types/portal';
|
|
16
|
+
import { useI18n } from '@portal/hooks';
|
|
17
|
+
|
|
18
|
+
import { LanguagePicker } from '../../I18n/LanguagePicker';
|
|
16
19
|
|
|
17
20
|
const EmptyNavbarHack = createGlobalStyle`
|
|
18
21
|
#redocly_root {
|
|
@@ -23,6 +26,7 @@ const EmptyNavbarHack = createGlobalStyle`
|
|
|
23
26
|
export function Navbar(): JSX.Element | null {
|
|
24
27
|
const [isOpen, setIsOpen] = useMobileMenu(false);
|
|
25
28
|
const themeConfig = useThemeConfig();
|
|
29
|
+
const { changeLanguage } = useI18n();
|
|
26
30
|
|
|
27
31
|
const menu = themeConfig.navbar?.items;
|
|
28
32
|
const logo = themeConfig.logo;
|
|
@@ -56,6 +60,7 @@ export function Navbar(): JSX.Element | null {
|
|
|
56
60
|
menu: menu as ResolvedConfigLinks,
|
|
57
61
|
logo: logo as Pick<LogoConfig, 'image' | 'link' | 'altText'>,
|
|
58
62
|
hideUserProfile,
|
|
63
|
+
changeLanguage,
|
|
59
64
|
}}
|
|
60
65
|
/>
|
|
61
66
|
);
|
|
@@ -69,6 +74,7 @@ interface NavbarPresentationalProps extends NavbarLogoProps {
|
|
|
69
74
|
hideSearch: boolean;
|
|
70
75
|
menu: ResolvedConfigLinks;
|
|
71
76
|
hideUserProfile: boolean | string | undefined;
|
|
77
|
+
changeLanguage: (value: string) => void;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Element | null {
|
|
@@ -81,6 +87,7 @@ export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Elem
|
|
|
81
87
|
logo,
|
|
82
88
|
menu,
|
|
83
89
|
hideUserProfile,
|
|
90
|
+
changeLanguage,
|
|
84
91
|
} = props;
|
|
85
92
|
return (
|
|
86
93
|
<NavbarContainer data-component-name="Navbar/Navbar">
|
|
@@ -97,6 +104,7 @@ export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Elem
|
|
|
97
104
|
<NavbarLogo logo={logo} />
|
|
98
105
|
<NavbarMenu menuItems={menu as ResolvedConfigLinks} />
|
|
99
106
|
{hideSearch ? null : <Search />}
|
|
107
|
+
<LanguagePicker onChangeLanguage={changeLanguage} />
|
|
100
108
|
{hideUserProfile ? null : <AuthUserProfile />}
|
|
101
109
|
<ColorModeSwitcher />
|
|
102
110
|
</NavbarRow>
|
|
@@ -3,17 +3,19 @@ import styled from 'styled-components';
|
|
|
3
3
|
|
|
4
4
|
import { Link } from '@portal/Link';
|
|
5
5
|
import type { ResolvedNavLinkItem } from '@theme/types/portal';
|
|
6
|
+
import { useTranslate } from '@portal/hooks';
|
|
6
7
|
|
|
7
8
|
interface NavbarDropdownProps {
|
|
8
9
|
items: ResolvedNavLinkItem[];
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function NavbarDropdown({ items }: NavbarDropdownProps): JSX.Element {
|
|
13
|
+
const { translate } = useTranslate();
|
|
12
14
|
return (
|
|
13
15
|
<DropdownWrapper data-component-name="Navbar/NavbarDropdown">
|
|
14
16
|
{items.map((item, index) => (
|
|
15
17
|
<div key={`${item.label}_${index}`}>
|
|
16
|
-
<Link to={item.link}>{item.label}</Link>
|
|
18
|
+
<Link to={item.link}>{translate(item.labelTranslationKey, item.label)}</Link>
|
|
17
19
|
</div>
|
|
18
20
|
))}
|
|
19
21
|
</DropdownWrapper>
|
|
@@ -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;
|
package/src/globalStyle.ts
CHANGED
|
@@ -1792,6 +1792,13 @@ const markdown = css`
|
|
|
1792
1792
|
--md-list-item-margin: 0.25rem; // @presenter Spacing
|
|
1793
1793
|
--md-list-item-style: inherit;
|
|
1794
1794
|
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* @tokens Markdown Checkbox List
|
|
1798
|
+
*/
|
|
1799
|
+
|
|
1800
|
+
--md-checkbox-list-item-style: none;
|
|
1801
|
+
|
|
1795
1802
|
/**
|
|
1796
1803
|
* @tokens Markdown Numbered List
|
|
1797
1804
|
*/
|
|
@@ -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
|
);
|