@redocly/theme 0.17.1 → 0.18.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 (126) hide show
  1. package/lib/I18n/LanguagePicker.js +53 -85
  2. package/lib/components/Catalog/Catalog.d.ts +1 -1
  3. package/lib/components/Catalog/useCatalog.d.ts +2 -1
  4. package/lib/components/Dropdown/Dropdown.d.ts +16 -0
  5. package/lib/components/Dropdown/Dropdown.js +113 -0
  6. package/lib/components/Dropdown/index.d.ts +1 -0
  7. package/lib/components/Dropdown/index.js +18 -0
  8. package/lib/components/Menu/MenuGroup.js +2 -0
  9. package/lib/components/Menu/MenuItem.js +2 -0
  10. package/lib/components/Menu/MobileMenu.d.ts +2 -9
  11. package/lib/components/Menu/MobileMenu.js +35 -65
  12. package/lib/components/Menu/MobileMenuGroup.js +2 -0
  13. package/lib/components/Menu/constants.d.ts +5 -0
  14. package/lib/components/Menu/constants.js +10 -0
  15. package/lib/components/Menu/hooks/use-mobile-menu-items.d.ts +2 -0
  16. package/lib/components/Menu/hooks/use-mobile-menu-items.js +83 -0
  17. package/lib/components/Menu/hooks/use-mobile-menu-levels.d.ts +5 -0
  18. package/lib/components/Menu/hooks/use-mobile-menu-levels.js +50 -0
  19. package/lib/components/Menu/types.d.ts +3 -0
  20. package/lib/components/Menu/types.js +3 -0
  21. package/lib/components/Menu/utils.d.ts +7 -0
  22. package/lib/components/Menu/utils.js +76 -0
  23. package/lib/components/Navbar/MobileUserProfile.d.ts +1 -1
  24. package/lib/components/Navbar/MobileUserProfile.js +4 -7
  25. package/lib/components/Navbar/Navbar.d.ts +0 -3
  26. package/lib/components/Navbar/Navbar.js +9 -12
  27. package/lib/components/Navbar/NavbarItem.d.ts +5 -1
  28. package/lib/components/Navbar/NavbarItem.js +15 -16
  29. package/lib/components/Navbar/NavbarMenu.js +1 -1
  30. package/lib/components/OpenApiDocs/Dropdown.js +5 -5
  31. package/lib/components/Product/Product.d.ts +7 -0
  32. package/lib/components/Product/Product.js +23 -0
  33. package/lib/components/Product/ProductPicker.d.ts +2 -0
  34. package/lib/components/Product/ProductPicker.js +83 -0
  35. package/lib/components/Product/index.d.ts +2 -0
  36. package/lib/components/Product/index.js +19 -0
  37. package/lib/components/Profile/UserProfile.js +54 -6
  38. package/lib/components/Search/InputWrapper.js +1 -7
  39. package/lib/components/Search/ProductTag.d.ts +6 -0
  40. package/lib/components/Search/ProductTag.js +61 -0
  41. package/lib/components/Search/SearchDialog.js +19 -4
  42. package/lib/components/Search/SearchItem.d.ts +2 -1
  43. package/lib/components/Search/SearchItem.js +13 -2
  44. package/lib/components/Search/index.d.ts +1 -0
  45. package/lib/components/Search/index.js +1 -0
  46. package/lib/components/Select/Select.d.ts +18 -0
  47. package/lib/components/Select/Select.js +118 -0
  48. package/lib/components/Select/index.d.ts +1 -0
  49. package/lib/components/Select/index.js +18 -0
  50. package/lib/components/Sidebar/DrilldownMenuItem.js +2 -9
  51. package/lib/components/Sidebar/SidebarItemIcon.d.ts +1 -0
  52. package/lib/components/Sidebar/SidebarItemIcon.js +16 -0
  53. package/lib/components/Sidebar/VersionPicker.d.ts +7 -0
  54. package/lib/components/Sidebar/VersionPicker.js +51 -0
  55. package/lib/components/Sidebar/index.d.ts +2 -0
  56. package/lib/components/Sidebar/index.js +2 -0
  57. package/lib/components/index.d.ts +3 -0
  58. package/lib/components/index.js +3 -0
  59. package/lib/config.d.ts +952 -25
  60. package/lib/config.js +115 -2
  61. package/lib/globalStyle.js +177 -16
  62. package/lib/hooks/useThemeConfig.d.ts +1 -1
  63. package/lib/hooks/useThemeConfig.js +7 -2
  64. package/lib/mocks/hooks/index.d.ts +4 -3
  65. package/lib/mocks/hooks/index.js +6 -2
  66. package/lib/mocks/search.d.ts +1 -1
  67. package/lib/mocks/search.js +1 -1
  68. package/lib/types/portal/src/shared/types/catalog.d.ts +2 -24
  69. package/lib/types/portal/src/shared/types/nav.d.ts +4 -0
  70. package/lib/types/portal/src/shared/types/searchDocument.d.ts +2 -0
  71. package/lib/ui/darkColors.js +2 -0
  72. package/lib/ui/index.d.ts +0 -1
  73. package/lib/ui/index.js +0 -1
  74. package/package.json +1 -1
  75. package/src/I18n/LanguagePicker.tsx +62 -89
  76. package/src/components/Catalog/Catalog.tsx +1 -1
  77. package/src/components/Catalog/useCatalog.ts +2 -3
  78. package/src/components/Dropdown/Dropdown.tsx +138 -0
  79. package/src/components/Dropdown/index.ts +1 -0
  80. package/src/components/Menu/MenuGroup.tsx +2 -0
  81. package/src/components/Menu/MenuItem.tsx +2 -0
  82. package/src/components/Menu/MobileMenu.tsx +43 -96
  83. package/src/components/Menu/MobileMenuGroup.tsx +2 -0
  84. package/src/components/Menu/constants.ts +5 -0
  85. package/src/components/Menu/hooks/use-mobile-menu-items.ts +100 -0
  86. package/src/components/Menu/hooks/use-mobile-menu-levels.ts +55 -0
  87. package/src/components/Menu/types.ts +3 -0
  88. package/src/components/Menu/utils.ts +109 -0
  89. package/src/components/Navbar/MobileUserProfile.tsx +19 -20
  90. package/src/components/Navbar/Navbar.tsx +12 -22
  91. package/src/components/Navbar/NavbarItem.tsx +20 -15
  92. package/src/components/Navbar/NavbarMenu.tsx +1 -1
  93. package/src/components/OpenApiDocs/Dropdown.tsx +5 -5
  94. package/src/components/Product/Product.tsx +28 -0
  95. package/src/components/Product/ProductPicker.tsx +97 -0
  96. package/src/components/Product/index.ts +2 -0
  97. package/src/components/Profile/UserProfile.tsx +80 -21
  98. package/src/components/Search/InputWrapper.tsx +1 -7
  99. package/src/components/Search/ProductTag.tsx +46 -0
  100. package/src/components/Search/SearchDialog.tsx +21 -5
  101. package/src/components/Search/SearchItem.tsx +17 -3
  102. package/src/components/Search/index.ts +1 -0
  103. package/src/components/Select/Select.tsx +140 -0
  104. package/src/components/Select/index.ts +1 -0
  105. package/src/components/Sidebar/DrilldownMenuItem.tsx +2 -10
  106. package/src/components/Sidebar/SidebarItemIcon.tsx +10 -0
  107. package/src/components/Sidebar/VersionPicker.tsx +48 -0
  108. package/src/components/Sidebar/index.ts +2 -0
  109. package/src/components/index.ts +3 -0
  110. package/src/config.ts +140 -6
  111. package/src/globalStyle.ts +181 -16
  112. package/src/hooks/useThemeConfig.ts +18 -3
  113. package/src/mocks/hooks/index.ts +9 -3
  114. package/src/mocks/search.ts +1 -1
  115. package/src/types/portal/src/shared/types/catalog.ts +2 -26
  116. package/src/types/portal/src/shared/types/i18n.d.ts +3 -1
  117. package/src/types/portal/src/shared/types/nav.ts +41 -37
  118. package/src/types/portal/src/shared/types/searchDocument.ts +7 -4
  119. package/src/ui/darkColors.tsx +2 -0
  120. package/src/ui/index.tsx +0 -1
  121. package/lib/components/Navbar/NavbarDropdown.d.ts +0 -9
  122. package/lib/components/Navbar/NavbarDropdown.js +0 -40
  123. package/lib/components/Profile/UserProfileMenu.d.ts +0 -8
  124. package/lib/components/Profile/UserProfileMenu.js +0 -93
  125. package/src/components/Navbar/NavbarDropdown.tsx +0 -48
  126. package/src/components/Profile/UserProfileMenu.tsx +0 -97
@@ -0,0 +1,100 @@
1
+ import { useLocation } from 'react-router-dom';
2
+
3
+ import type { ResolvedNavItem } from '@theme/types/portal';
4
+ import { useCurrentProduct, useI18nConfig, usePageVersions, useTranslate } from '@portal/hooks';
5
+ import { useSidebarItems } from '@portal/Sidebar/useSidebarItems';
6
+ import { useThemeConfig } from '@theme/hooks';
7
+
8
+ import { MenuType } from '../constants';
9
+ import { buildLanguagesGroup, buildVersionSection, mapNavbarItems } from '../utils';
10
+
11
+ const MENU_SEPARATOR: ResolvedNavItem = {
12
+ type: 'separator',
13
+ separatorLine: true,
14
+ };
15
+
16
+ const TRANSLATION_KEYS = {
17
+ products: 'theme.mobileMenu.products',
18
+ };
19
+
20
+ export const useMobileMenuItems = (menuType: MenuType) => {
21
+ const { versions = [] } = usePageVersions() || {};
22
+ const { defaultLocale, currentLocale, locales } = useI18nConfig();
23
+ const { translate } = useTranslate();
24
+ const activeVersion = versions.find((version) => version.active);
25
+ const sidebarItems = useSidebarItems(activeVersion);
26
+ const location = useLocation();
27
+
28
+ const currentProduct = useCurrentProduct();
29
+ const hasProductNavbarOverride = currentProduct?.themeOverride?.navbar;
30
+
31
+ const productThemeConfig = useThemeConfig();
32
+ const baseThemeConfig = useThemeConfig(false);
33
+
34
+ const baseNavItems = (baseThemeConfig.navbar?.items || []) as ResolvedNavItem[];
35
+ const productNavItems = (
36
+ hasProductNavbarOverride ? productThemeConfig.navbar?.items || [] : []
37
+ ) as ResolvedNavItem[];
38
+
39
+ const menuItems: ResolvedNavItem[] = [];
40
+
41
+ if (menuType === MenuType.PAGE) {
42
+ if (activeVersion) {
43
+ // TODO: consider to refactor
44
+ const versionsSection = buildVersionSection(translate, versions, activeVersion);
45
+ menuItems.push(...versionsSection);
46
+ }
47
+ if (menuItems.length && sidebarItems.length) {
48
+ menuItems.push(MENU_SEPARATOR);
49
+ }
50
+ menuItems.push(...(sidebarItems as ResolvedNavItem[]));
51
+ } else if (menuType === MenuType.PRODUCT) {
52
+ menuItems.push(...productNavItems);
53
+
54
+ if (menuItems.length && baseNavItems.length) {
55
+ menuItems.push(MENU_SEPARATOR);
56
+ }
57
+
58
+ if (baseNavItems.length) {
59
+ menuItems.push(...baseNavItems);
60
+ }
61
+ } else if (menuType === MenuType.MAIN_MENU) {
62
+ const productMenuItems: ResolvedNavItem[] = Object.values(baseThemeConfig.products || {}).map(
63
+ (product) => ({
64
+ label: product.name,
65
+ link: product.link,
66
+ icon: product.icon,
67
+ type: 'link',
68
+ }),
69
+ );
70
+
71
+ if (productMenuItems.length) {
72
+ menuItems.push({
73
+ type: 'separator',
74
+ label: translate(TRANSLATION_KEYS.products, 'Products'),
75
+ });
76
+
77
+ menuItems.push(...productMenuItems);
78
+ }
79
+
80
+ if (menuItems.length && baseNavItems.length) {
81
+ menuItems.push(MENU_SEPARATOR);
82
+ }
83
+
84
+ if (baseNavItems.length) {
85
+ menuItems.push(...baseNavItems);
86
+ }
87
+ }
88
+
89
+ const languagesMenu = buildLanguagesGroup(locales, defaultLocale, currentLocale);
90
+
91
+ if (menuItems.length && languagesMenu) {
92
+ menuItems.push(MENU_SEPARATOR);
93
+ }
94
+
95
+ if (languagesMenu) {
96
+ menuItems.push(languagesMenu);
97
+ }
98
+
99
+ return mapNavbarItems(menuItems, defaultLocale, currentLocale, locales, location);
100
+ };
@@ -0,0 +1,55 @@
1
+ import { useMemo } from 'react';
2
+ import { useLocation } from 'react-router-dom';
3
+
4
+ import { useCurrentProduct, usePageVersions, useTranslate } from '@portal/hooks';
5
+ import { useSidebarItems } from '@portal/Sidebar/useSidebarItems';
6
+ import { MenuType } from '@theme/components/Menu/constants';
7
+ import { useThemeConfig } from '@theme/hooks';
8
+ import type { ResolvedNavItem } from '@theme/types/portal';
9
+
10
+ const TRANSLATION_KEYS = {
11
+ mainMenu: 'theme.mobileMenu.mainMenu',
12
+ previous: 'theme.mobileMenu.previous',
13
+ };
14
+
15
+ export const useMobileMenuLevels = () => {
16
+ const { versions = [] } = usePageVersions() || {};
17
+ const activeVersion = versions.find((version) => version?.active);
18
+ const sidebarItems = useSidebarItems(activeVersion);
19
+ const { translate } = useTranslate();
20
+ const currentProduct = useCurrentProduct();
21
+ const productThemeConfig = useThemeConfig();
22
+ const location = useLocation();
23
+
24
+ const hasProductNavbarOverride = currentProduct?.themeOverride?.navbar;
25
+ const productNavItems = (
26
+ hasProductNavbarOverride ? productThemeConfig.navbar?.items || [] : []
27
+ ) as ResolvedNavItem[];
28
+
29
+ const menuLevels = useMemo(() => {
30
+ const menuLevels = [
31
+ {
32
+ label: translate(TRANSLATION_KEYS.mainMenu, 'Main Menu'),
33
+ type: MenuType.MAIN_MENU,
34
+ },
35
+ ];
36
+
37
+ if (currentProduct && productNavItems.length) {
38
+ menuLevels.push({
39
+ label: currentProduct.name,
40
+ type: MenuType.PRODUCT,
41
+ });
42
+ }
43
+
44
+ if (sidebarItems.length || activeVersion) {
45
+ menuLevels.push({
46
+ label: translate(TRANSLATION_KEYS.previous, 'Previous'),
47
+ type: MenuType.PAGE,
48
+ });
49
+ }
50
+ return menuLevels;
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, [location]);
53
+
54
+ return menuLevels;
55
+ };
@@ -0,0 +1,3 @@
1
+ export interface MobileMenuProps {
2
+ className?: string;
3
+ }
@@ -0,0 +1,109 @@
1
+ import type { Location } from 'react-router-dom';
2
+
3
+ import type { ItemState } from '@theme/components/Sidebar/types';
4
+ import type { Locale, ResolvedNavItem, TFunction, Version } from '@theme/types/portal';
5
+ import { getPathnameForLocale } from '@portal/utils';
6
+ import { withPathPrefix } from '@theme/utils/urls';
7
+
8
+ const TRANSLATION_KEYS = {
9
+ version: 'theme.mobileMenu.version',
10
+ };
11
+
12
+ export const mapNavbarItems = (
13
+ items: ResolvedNavItem[],
14
+ defaultLocale: string,
15
+ currentLocale: string,
16
+ locales: Locale[],
17
+ location: Location,
18
+ ): ItemState[] => {
19
+ return items.map(
20
+ (navItem) =>
21
+ ({
22
+ ...navItem,
23
+ ...(navItem.items && {
24
+ items: mapNavbarItems(navItem.items, defaultLocale, currentLocale, locales, location),
25
+ }),
26
+ ...('link' in navItem && { link: navItem.link || '/' }),
27
+ active:
28
+ 'link' in navItem &&
29
+ isItemActive(navItem, defaultLocale, currentLocale, locales, location),
30
+ hasActiveSubItem: !!navItem.items?.find((item) =>
31
+ isItemActive(item, defaultLocale, currentLocale, locales, location),
32
+ ),
33
+ } as ItemState),
34
+ );
35
+ };
36
+
37
+ export const isItemActive = (
38
+ item: ResolvedNavItem,
39
+ defaultLocale: string,
40
+ currentLocale: string,
41
+ locales: Locale[],
42
+ location: Location,
43
+ ): boolean => {
44
+ const pathHash = location.pathname + location.hash;
45
+ const link = item.languageInsensitive
46
+ ? item.link || ''
47
+ : getPathnameForLocale(item.link || '/', defaultLocale, currentLocale, locales);
48
+
49
+ return pathHash === withPathPrefix(link);
50
+ };
51
+
52
+ export const buildLanguagesGroup = (
53
+ locales: Locale[],
54
+ currentLocale: string,
55
+ defaultLocale: string,
56
+ ): ResolvedNavItem | undefined => {
57
+ if (locales.length < 2) {
58
+ return;
59
+ }
60
+ const locale = locales.find((l) => l.code === currentLocale);
61
+ return {
62
+ type: 'group',
63
+ label: locale?.name || locale?.code,
64
+ items: locales
65
+ .filter((locale) => locale.code !== currentLocale)
66
+ .map((locale) => {
67
+ const newLangPathname = withPathPrefix(
68
+ getPathnameForLocale(location.pathname, defaultLocale, locale.code, locales),
69
+ );
70
+ const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
71
+ return {
72
+ type: 'link',
73
+ label: locale.name || locale.code || '',
74
+ link: newUrlWithLanguage,
75
+ active: false,
76
+ hasActiveSubItem: false,
77
+ languageInsensitive: true,
78
+ };
79
+ }),
80
+ };
81
+ };
82
+
83
+ export const buildVersionSection = (
84
+ translate: TFunction,
85
+ versions: Version[],
86
+ activeVersion?: Version,
87
+ ): ResolvedNavItem[] => {
88
+ return [
89
+ {
90
+ type: 'separator',
91
+ label: translate(TRANSLATION_KEYS.version, 'Version'),
92
+ },
93
+ {
94
+ type: 'group',
95
+ label: activeVersion?.label,
96
+ items: versions
97
+ .filter((version) => !version.active)
98
+ .map((version) => {
99
+ return {
100
+ type: 'link',
101
+ label: version.label,
102
+ link: version.link,
103
+ active: false,
104
+ hasActiveSubItem: false,
105
+ };
106
+ }),
107
+ },
108
+ ];
109
+ };
@@ -7,7 +7,7 @@ import { AvatarWrapper, ProfileWrapper, Profile } from '@theme/components/Profil
7
7
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
8
8
  import { useTranslate } from '@portal/hooks';
9
9
 
10
- export function MobileUserProfile(): JSX.Element {
10
+ export function MobileUserProfile() {
11
11
  const { userProfile } = useThemeConfig();
12
12
  const { userData, handleLogout, loginUrl } = useProfileProps();
13
13
  const { translate } = useTranslate();
@@ -15,27 +15,27 @@ export function MobileUserProfile(): JSX.Element {
15
15
  login: 'theme.profile.login',
16
16
  };
17
17
 
18
- if (!userData?.isAuthenticated) {
19
- return (
20
- <MobileProfileWrapper data-component-name="Navbar/MobileUserProfile">
21
- <LoginButton href={loginUrl} data-cy="login-btn">
22
- {translate(translationKeys.login, userProfile?.loginLabel || 'Login')}
23
- </LoginButton>
24
- </MobileProfileWrapper>
25
- );
26
- }
18
+ if (!userData?.isAuthenticated && !loginUrl) return null;
27
19
 
28
20
  return (
29
21
  <MobileProfileWrapper data-component-name="Navbar/MobileUserProfile">
30
- <UserDataWrapper>
31
- <ProfilePicture>
32
- <Profile name={userData.name} imageUrl={userData.picture} />
33
- </ProfilePicture>
34
- <UserName>{userData.name}</UserName>
35
- </UserDataWrapper>
36
- <LogoutButton onClick={() => handleLogout()}>
37
- <LogoutIcon />
38
- </LogoutButton>
22
+ {!userData?.isAuthenticated ? (
23
+ <LoginButton href={loginUrl} data-cy="login-btn">
24
+ {translate(translationKeys.login, userProfile?.loginLabel || 'Login')}
25
+ </LoginButton>
26
+ ) : (
27
+ <>
28
+ <UserDataWrapper>
29
+ <ProfilePicture>
30
+ <Profile name={userData.name} imageUrl={userData.picture} />
31
+ </ProfilePicture>
32
+ <UserName>{userData.name}</UserName>
33
+ </UserDataWrapper>
34
+ <LogoutButton onClick={() => handleLogout()}>
35
+ <LogoutIcon />
36
+ </LogoutButton>
37
+ </>
38
+ )}
39
39
  </MobileProfileWrapper>
40
40
  );
41
41
  }
@@ -58,7 +58,6 @@ const MobileProfileWrapper = styled.div`
58
58
  justify-content: space-between;
59
59
  align-items: center;
60
60
  width: 100%;
61
- position: fixed;
62
61
  bottom: 0;
63
62
  padding: 18px 16px;
64
63
  border-top: 1px solid var(--mobile-menu-profile-border-color);
@@ -3,7 +3,6 @@ import styled, { createGlobalStyle } from 'styled-components';
3
3
 
4
4
  import { NavbarMenu } from '@theme/components/Navbar';
5
5
  import { useMobileMenu } from '@theme/hooks/useMobileMenu';
6
- import { isEmptyArray, isPrimitive } from '@theme/utils';
7
6
  import type { NavbarLogoProps } from '@theme/components/NavbarLogo';
8
7
  import { NavbarLogo } from '@theme/components/NavbarLogo';
9
8
  import { MobileMenu } from '@theme/components/Menu/MobileMenu';
@@ -11,12 +10,11 @@ import { ColorModeSwitcher } from '@theme/components/ColorModeSwitcher/ColorMode
11
10
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
12
11
  import { Search } from '@theme/components/Search/Search';
13
12
  import { AuthUserProfile } from '@theme/components/Profile/AuthUserProfile';
14
- import type { LogoConfig, ResolvedConfigLinks, ResolvedNavItem } from '@theme/types/portal';
15
- import { useI18n, usePageVersions } from '@portal/hooks';
13
+ import type { LogoConfig, ResolvedConfigLinks } from '@theme/types/portal';
14
+ import { useI18n } from '@portal/hooks';
15
+ import { LanguagePicker } from '@theme/I18n/LanguagePicker';
16
+ import { ProductPicker } from '@theme/components/Product';
16
17
  import { BurgerButton } from '@theme/components/Navbar/BurgerButton';
17
- import { useSidebarItems } from '@portal/Sidebar/useSidebarItems';
18
-
19
- import { LanguagePicker } from '../../I18n/LanguagePicker';
20
18
 
21
19
  const EmptyNavbarHack = createGlobalStyle`
22
20
  :root {
@@ -44,8 +42,6 @@ export function Navbar(): JSX.Element | null {
44
42
  const openMobileMenu = () => setIsOpen(true);
45
43
  const closeMobileMenu = () => setIsOpen(false);
46
44
 
47
- const menuItemsExist = !isPrimitive(menu) && !isEmptyArray(menu);
48
-
49
45
  if (!menu && !logo) {
50
46
  return <EmptyNavbarHack />;
51
47
  }
@@ -53,12 +49,10 @@ export function Navbar(): JSX.Element | null {
53
49
  return (
54
50
  <NavbarPresentational
55
51
  {...{
56
- menuItemsExist,
57
52
  closeMobileMenu,
58
53
  openMobileMenu,
59
54
  isOpen,
60
55
  hideSearch: Boolean(hideSearch),
61
- menu: menu as ResolvedConfigLinks,
62
56
  logo: logo as Pick<LogoConfig, 'image' | 'link' | 'altText'>,
63
57
  hideUserProfile,
64
58
  changeLanguage,
@@ -68,42 +62,38 @@ export function Navbar(): JSX.Element | null {
68
62
  }
69
63
 
70
64
  interface NavbarPresentationalProps extends NavbarLogoProps {
71
- menuItemsExist: boolean;
72
65
  closeMobileMenu: () => void;
73
66
  openMobileMenu: () => void;
74
67
  isOpen: boolean;
75
68
  hideSearch: boolean;
76
- menu: ResolvedConfigLinks;
77
69
  hideUserProfile: boolean | string | undefined;
78
70
  changeLanguage: (value: string) => void;
79
71
  className?: string;
80
72
  }
81
73
 
82
74
  export function NavbarPresentational(props: NavbarPresentationalProps): JSX.Element | null {
83
- const { versions = [] } = usePageVersions() || {};
84
- const activeVersion = versions.find((version) => version?.active);
85
- const sidebarItems = useSidebarItems(activeVersion);
86
-
87
75
  const {
88
- menuItemsExist,
89
76
  closeMobileMenu,
90
77
  openMobileMenu,
91
78
  isOpen,
92
79
  hideSearch,
93
80
  logo,
94
- menu,
95
81
  hideUserProfile,
96
82
  changeLanguage,
97
83
  className,
98
84
  } = props;
85
+
86
+ const themeConfig = useThemeConfig();
87
+
88
+ const menu = themeConfig.navbar?.items;
89
+
99
90
  return (
100
91
  <NavbarContainer data-component-name="Navbar/Navbar" className={className}>
101
- {isOpen && <MobileMenu navItems={menu as ResolvedNavItem[]} sidebarItems={sidebarItems} />}
92
+ {isOpen && <MobileMenu />}
102
93
  <NavbarRow>
103
- {menuItemsExist && (
104
- <BurgerButton onClick={isOpen ? closeMobileMenu : openMobileMenu} isOpen={isOpen} />
105
- )}
94
+ <BurgerButton onClick={isOpen ? closeMobileMenu : openMobileMenu} isOpen={isOpen} />
106
95
  <NavbarLogo logo={logo} />
96
+ <ProductPicker />
107
97
  <NavbarMenu menuItems={menu as ResolvedConfigLinks} />
108
98
  {hideSearch ? null : <Search />}
109
99
  <LanguagePicker onChangeLanguage={changeLanguage} />
@@ -4,7 +4,6 @@ import { useLocation } from 'react-router-dom';
4
4
 
5
5
  import { Link } from '@portal/Link';
6
6
  import { getPathnameForLocale } from '@portal/utils';
7
- import { NavbarDropdown, DropdownWrapper } from '@theme/components/Navbar/NavbarDropdown';
8
7
  import type {
9
8
  ResolvedNavItem,
10
9
  ResolvedNavLinkItem,
@@ -12,6 +11,7 @@ import type {
12
11
  } from '@theme/types/portal';
13
12
  import { useI18nConfig, useTranslate } from '@portal/hooks';
14
13
  import { withPathPrefix } from '@theme/utils';
14
+ import { Dropdown } from '@theme/components/Dropdown';
15
15
 
16
16
  export interface NavbarItemProps {
17
17
  navItem: ResolvedNavItem;
@@ -45,8 +45,15 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
45
45
 
46
46
  if ((navItem as ResolvedNavGroupItem).items) {
47
47
  const item = navItem as ResolvedNavGroupItem;
48
+ const groupItems = item.items as ResolvedNavLinkItem[];
49
+ const groupItemsComponents = groupItems.map((item, index) => (
50
+ <Link key={`${item.label}_${index}`} to={item.link}>
51
+ {translate(item.labelTranslationKey, item.label)}
52
+ </Link>
53
+ ));
54
+
48
55
  return (
49
- <NavbarMenuItemWithDropdownWrapper>
56
+ <NavbarMenuItemDropdown items={groupItemsComponents} triggerEvent="hover">
50
57
  <NavbarMenuItem
51
58
  active={false}
52
59
  data-component-name="Navbar/NavbarItem"
@@ -55,23 +62,21 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
55
62
  <NavbarIcon url={item.icon} />
56
63
  <NavbarLabel>{translate(item.labelTranslationKey, item.label)}</NavbarLabel>
57
64
  </NavbarMenuItem>
58
- <NavbarDropdown items={item.items as ResolvedNavLinkItem[]} />
59
- </NavbarMenuItemWithDropdownWrapper>
65
+ </NavbarMenuItemDropdown>
60
66
  );
61
67
  }
62
68
 
63
69
  return null;
64
70
  }
65
71
 
66
- export const NavbarMenuItemWithDropdownWrapper = styled.div`
67
- display: inline-block;
68
- position: relative;
69
- border-radius: var(--navbar-item-border-radius);
70
- &:hover {
71
- background: var(--navbar-item-active-background-color);
72
- ${DropdownWrapper} {
73
- display: block;
74
- }
72
+ export const NavbarMenuItemDropdown = styled(Dropdown).attrs(() => ({
73
+ dataAttributes: {
74
+ 'data-component-name': 'Navbar/NavbarItemDropdown',
75
+ },
76
+ }))`
77
+ :hover {
78
+ text-decoration: var(--navbar-item-hover-text-decoration);
79
+ background: var(--navbar-item-hover-background-color);
75
80
  }
76
81
  `;
77
82
 
@@ -85,14 +90,14 @@ export const NavbarLink = styled(Link)`
85
90
  export const NavbarMenuItem = styled.li<{ active?: boolean }>`
86
91
  display: inline-block;
87
92
  padding: var(--navbar-item-padding-vertical) var(--navbar-item-padding-horizontal);
88
- margin-left: var(--navbar-item-margin-horizontal);
89
93
  text-align: center;
90
94
  line-height: 1;
91
95
  font-size: var(--navbar-item-font-size);
92
96
  border-radius: var(--navbar-item-border-radius);
93
97
  font-weight: var(--navbar-item-font-weight);
94
98
  background: ${({ active }) => active && 'var(--navbar-item-active-background-color)'};
95
- color: ${({ active }) => active && 'var(--navbar-item-active-text-color)'};
99
+ color: ${({ active }) =>
100
+ active ? 'var(--navbar-item-active-text-color)' : 'var(--navbar-text-color)'};
96
101
  &:hover {
97
102
  color: var(--navbar-item-hover-text-color);
98
103
  text-decoration: var(--navbar-item-hover-text-decoration);
@@ -29,13 +29,13 @@ export function NavbarMenu({
29
29
 
30
30
  const NavItemsContainer = styled.ul`
31
31
  list-style: none;
32
- margin: 0 var(--navbar-item-margin-horizontal);
33
32
  padding: 0;
34
33
  display: none;
35
34
  flex-wrap: nowrap;
36
35
  align-items: center;
37
36
  justify-content: flex-end;
38
37
  flex: 1;
38
+ gap: var(--navbar-item-gap);
39
39
 
40
40
  ${({ theme }) => theme.mediaQueries.medium} {
41
41
  display: flex;
@@ -137,15 +137,15 @@ box-sizing: border-box;
137
137
  height: 36px;
138
138
  outline: none;
139
139
  display: inline-block;
140
- color: var(--dropdown-text-color);
140
+ color: var(--docs-dropdown-text-color);
141
141
  border-radius: var(--border-radius);
142
- border: var(--dropdown-border);
143
- padding: var(--dropdown-padding);
142
+ border: var(--docs-dropdown-border);
143
+ padding: var(--docs-dropdown-padding);
144
144
  vertical-align: bottom;
145
145
  width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
146
146
  text-transform: none;
147
147
  line-height: inherit;
148
- font-size: var(--dropdown-font-size);
148
+ font-size: var(--docs-dropdown-font-size);
149
149
  font-family: inherit;
150
150
  text-overflow: ellipsis;
151
151
  overflow: hidden;
@@ -176,7 +176,7 @@ box-sizing: border-box;
176
176
  line-height: inherit;
177
177
  font-size: 14px;
178
178
  font-family: inherit;
179
- padding: var(--dropdown-padding);
179
+ padding: var(--docs-dropdown-padding);
180
180
  ${({ variant }) => (variant === 'dark' ? darkDropdownStyle : '')};
181
181
 
182
182
  `;
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { ProductConfig } from '@theme/config';
5
+
6
+ export type ProductProps = {
7
+ product: ProductConfig;
8
+ className?: string;
9
+ };
10
+
11
+ export const Product = ({ product, className }: ProductProps): JSX.Element | null => {
12
+ return (
13
+ <ProductWrapper data-component-name="Product/Product" className={className}>
14
+ <ProductLogo src={product.icon} />
15
+ <span>{product.name}</span>
16
+ </ProductWrapper>
17
+ );
18
+ };
19
+
20
+ const ProductWrapper = styled.div`
21
+ display: flex;
22
+ align-items: center;
23
+ `;
24
+
25
+ const ProductLogo = styled.img`
26
+ margin-right: var(--product-logo-margin);
27
+ max-height: var(--select-line-height);
28
+ `;
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { ThemeUIConfig } from '../../config';
5
+
6
+ import { useCurrentProduct, useGlobalData } from '@portal/hooks';
7
+ import { Link } from '@portal/Link';
8
+ import { ArrowIcon } from '@theme/icons';
9
+
10
+ import { Select, SelectInput, SelectList, SelectListItem } from '../Select';
11
+ import { Product } from './Product';
12
+
13
+ export const ProductPicker = () => {
14
+ const globalData = useGlobalData() as { theme?: ThemeUIConfig };
15
+ const currentProduct = useCurrentProduct();
16
+ const currentProductComponent = currentProduct ? (
17
+ <Product product={currentProduct} />
18
+ ) : (
19
+ 'Products'
20
+ );
21
+
22
+ const products = Object.values(globalData?.theme?.products || {});
23
+
24
+ const productComponents = products.map((product) => (
25
+ <Link key={product.slug} to={product.link}>
26
+ <Product product={product} />
27
+ </Link>
28
+ ));
29
+
30
+ return products.length ? (
31
+ <ProductSelect
32
+ selected={currentProductComponent}
33
+ options={productComponents}
34
+ withArrow={true}
35
+ triggerEvent="hover"
36
+ />
37
+ ) : null;
38
+ };
39
+
40
+ const ProductSelect = styled(Select).attrs(() => ({
41
+ dataAttributes: {
42
+ 'data-component-name': 'Product/ProductPicker',
43
+ },
44
+ }))`
45
+ display: none;
46
+ font-size: var(--product-picker-font-size);
47
+ font-weight: var(--product-picker-font-weight);
48
+ line-height: var(--product-picker-line-height);
49
+ border-radius: var(--product-picker-border-radius);
50
+ color: var(--product-picker-text-color);
51
+
52
+ ${SelectInput} {
53
+ border-radius: var(--product-picker-input-border-radius);
54
+ padding: var(--product-picker-input-padding-vertical)
55
+ var(--product-picker-input-padding-horizontal);
56
+ color: var(--navbar-text-color);
57
+
58
+ a {
59
+ color: var(--navbar-text-color);
60
+ }
61
+ }
62
+
63
+ ${SelectList} {
64
+ min-width: var(--product-picker-list-min-width);
65
+ max-width: var(--product-picker-list-max-width);
66
+ padding: var(--product-picker-list-padding);
67
+ background-color: var(--product-picker-list-background-color);
68
+ border-radius: var(--product-picker-list-border-radius);
69
+ box-shadow: var(--product-picker-list-box-shadow);
70
+ }
71
+
72
+ ${SelectListItem} {
73
+ border-radius: var(--product-picker-list-item-border-radius);
74
+
75
+ & > * {
76
+ padding: var(--product-picker-list-item-vertical-padding)
77
+ var(--product-picker-list-item-horizontal-padding);
78
+ }
79
+
80
+ :hover {
81
+ background-color: var(--product-picker-list-item-active-background-color);
82
+ }
83
+ }
84
+
85
+ ${({ theme }) => theme.mediaQueries.medium} {
86
+ display: block;
87
+ }
88
+
89
+ :hover {
90
+ text-decoration: var(--navbar-item-hover-text-decoration);
91
+ background: var(--navbar-item-hover-background-color);
92
+ }
93
+
94
+ ${ArrowIcon} {
95
+ fill: var(--navbar-text-color);
96
+ }
97
+ `;
@@ -0,0 +1,2 @@
1
+ export * from '@theme/components/Product/ProductPicker';
2
+ export * from '@theme/components/Product/Product';