@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
@@ -1,22 +1,13 @@
1
- import React, { useRef, useState } from 'react';
2
- import { useLocation } from 'react-router-dom';
1
+ import React from 'react';
3
2
  import styled from 'styled-components';
4
3
 
5
4
  import { getPathnameForLocale, withPathPrefix } from '@portal/utils';
6
5
  import { usePreloadHistory } from '@portal/usePreloadHistory';
7
6
  import { useI18nConfig } from '@portal/hooks';
8
-
9
- import { useOutsideClick } from '../hooks';
7
+ import { Select, SelectInput, SelectList, SelectListItem } from '@theme/components/Select';
10
8
 
11
9
  export const LanguagePicker = (props: { onChangeLanguage: (newLang: string) => void }) => {
12
10
  const { currentLocale, locales, defaultLocale } = useI18nConfig();
13
-
14
- const dropdownRef = useRef<HTMLDivElement | null>(null);
15
-
16
- const [isOpen, setIsOpen] = useState(false);
17
- useOutsideClick(dropdownRef, () => setIsOpen(false));
18
-
19
- const location = useLocation();
20
11
  const history = usePreloadHistory();
21
12
 
22
13
  if (locales.length < 2) {
@@ -28,93 +19,75 @@ export const LanguagePicker = (props: { onChangeLanguage: (newLang: string) => v
28
19
  return null;
29
20
  }
30
21
 
31
- return (
32
- <Dropdown
33
- data-component-name="I18n/LanguagePicker"
34
- ref={dropdownRef}
35
- isOpen={isOpen}
36
- onClick={() => setIsOpen(!isOpen)}
37
- onBlur={() => setIsOpen(false)}
22
+ const selected = locale.name || locale.code;
23
+ const languageItems = locales.map((locale) => (
24
+ <LanguageItem
25
+ onClick={() => {
26
+ const newLangPathname = withPathPrefix(
27
+ getPathnameForLocale(location.pathname, defaultLocale, locale.code, locales),
28
+ );
29
+ const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
30
+ history.push(newUrlWithLanguage);
31
+ props.onChangeLanguage(locale.code);
32
+ }}
33
+ key={locale.code}
34
+ role="link"
38
35
  >
39
- <DropdownValue role="link">{locale.name || locale.code}</DropdownValue>
40
- <DropdownMenu>
41
- {locales.map((locale) => (
42
- <MenuItem
43
- onClick={() => {
44
- const newLangPathname = withPathPrefix(
45
- getPathnameForLocale(location.pathname, defaultLocale, locale.code, locales),
46
- );
47
- const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
48
- history.push(newUrlWithLanguage);
49
- props.onChangeLanguage(locale.code);
50
- }}
51
- key={locale.code}
52
- role="link"
53
- >
54
- {locale.name || locale.code || ''}
55
- </MenuItem>
56
- ))}
57
- </DropdownMenu>
58
- </Dropdown>
59
- );
36
+ {locale.name || locale.code || ''}
37
+ </LanguageItem>
38
+ ));
39
+
40
+ return <LanguageSelect selected={selected} options={languageItems} triggerEvent="hover" />;
60
41
  };
61
42
 
62
- const Dropdown = styled.div<{ isOpen: boolean }>`
63
- font-size: var(--profile-menu-item-font-size);
64
- font-family: var(--profile-menu-item-font-family);
65
- font-weight: var(--profile-menu-item-font-weight);
66
- line-height: var(--profile-menu-item-line-height);
43
+ const LanguageSelect = styled(Select).attrs(() => ({
44
+ dataAttributes: {
45
+ 'data-component-name': 'I18n/LanguagePicker',
46
+ },
47
+ }))`
48
+ display: none;
49
+ font-size: var(--language-picker-font-size);
50
+ font-weight: var(--language-picker-font-weight);
51
+ line-height: var(--language-picker-line-height);
52
+ border-radius: var(--language-picker-border-radius);
53
+ color: var(--language-picker-text-color);
54
+
55
+ ${SelectInput} {
56
+ color: var(--navbar-text-color);
57
+ border-radius: var(--language-picker-input-border-radius);
58
+ padding: var(--language-picker-input-padding-vertical)
59
+ var(--language-picker-input-padding-horizontal);
60
+ }
67
61
 
68
- ${(props) =>
69
- props.isOpen
70
- ? `
71
- ${DropdownMenu} {
72
- display: block;
73
- z-index: var(--z-index-popover);
74
- }
75
- `
76
- : ``}
77
- `;
62
+ ${SelectList} {
63
+ min-width: var(--language-picker-list-min-width);
64
+ max-width: var(--language-picker-list-max-width);
65
+ padding: var(--language-picker-list-padding);
66
+ background-color: var(--language-picker-list-background-color);
67
+ border-radius: var(--language-picker-list-border-radius);
68
+ box-shadow: var(--language-picker-list-box-shadow);
69
+ }
78
70
 
79
- const DropdownValue = styled.div`
80
- cursor: pointer;
81
- display: block;
71
+ ${SelectListItem} {
72
+ border-radius: var(--language-picker-list-item-border-radius);
82
73
 
83
- color: var(--navbar-text-color);
84
- padding: var(--navbar-item-padding-vertical) var(--navbar-item-padding-horizontal);
85
- border-radius: var(--navbar-item-border-radius);
74
+ & > * {
75
+ padding: var(--language-picker-list-item-vertical-padding)
76
+ var(--language-picker-list-item-horizontal-padding);
77
+ }
86
78
 
87
- &:hover {
88
- color: var(--navbar-item-hover-text-color);
89
- text-decoration: var(--navbar-item-hover-text-decoration);
90
- background: var(--navbar-item-hover-background-color);
79
+ :hover {
80
+ background-color: var(--language-picker-list-item-active-background-color);
81
+ }
91
82
  }
92
- `;
93
-
94
- const DropdownMenu = styled.ul`
95
- position: absolute;
96
- color: var(--profile-menu-item-text-color);
97
- background-color: var(--profile-menu-background-color);
98
- top: var(--navbar-height);
99
- margin: 0;
100
- box-shadow: 0 1px 2px rgb(204, 204, 204);
101
- border-radius: 0 1px 2px 2px;
102
- overflow: hidden;
103
- display: none;
104
- overflow-y: auto;
105
- z-index: var(--z-index-surface);
106
- padding: 0;
107
- list-style: none;
108
- `;
109
83
 
110
- const MenuItem = styled.li`
111
- padding: 10px;
112
- transition: all 0.2s ease-in-out;
113
- cursor: pointer;
84
+ ${({ theme }) => theme.mediaQueries.medium} {
85
+ display: block;
86
+ }
114
87
 
115
- &:hover {
116
- color: var(--profile-menu-item-hover-text-color);
117
- text-decoration: var(--profile-menu-item-hover-text-decoration);
118
- background: var(--profile-menu-item-hover-background-color);
88
+ :hover {
89
+ text-decoration: var(--navbar-item-hover-text-decoration);
90
+ background: var(--navbar-item-hover-background-color);
119
91
  }
120
92
  `;
93
+ const LanguageItem = styled.div``;
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { ResolvedNavItem } from '@theme/types/portal';
5
- import type { CatalogConfig } from '@theme/types/portal/src/shared/types/catalog';
6
5
  import { HighlightContext } from '@theme/ui/Highlight';
7
6
  import { Button } from '@theme/components/Button';
8
7
  import { Filter } from '@theme/components/Filter';
@@ -10,6 +9,7 @@ import { H2 } from '@theme/components/Typography/H2';
10
9
  import { H3 } from '@theme/components/Typography/H3';
11
10
  import { CatalogCard } from '@theme/components/Catalog/CatalogCard';
12
11
  import { usePageSharedData, useTranslate } from '@portal/hooks/index';
12
+ import type { CatalogConfig } from '@theme/config';
13
13
 
14
14
  import { useCatalog } from './useCatalog';
15
15
 
@@ -5,13 +5,12 @@ import type { Location } from 'react-router-dom';
5
5
 
6
6
  import type { ResolvedNavItem } from '@theme/types/portal';
7
7
  import type {
8
- CatalogConfig,
9
8
  CatalogItem,
10
- Filter,
11
9
  FilteredCatalog,
12
10
  ResolvedFilter,
13
11
  } from '@theme/types/portal/src/shared/types/catalog';
14
12
  import { findDeepFirst, toStringIfDefined, withoutHash } from '@theme/utils';
13
+ import type { CatalogConfig, CatalogFilterConfig } from '@theme/config';
15
14
 
16
15
  export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): FilteredCatalog {
17
16
  const location = useLocation();
@@ -226,7 +225,7 @@ function normalizeItems(items: ResolvedNavItem[], config: CatalogConfig): Catalo
226
225
  });
227
226
  }
228
227
 
229
- function collectFilterParents(filtersWithOptions: Filter[] | undefined) {
228
+ function collectFilterParents(filtersWithOptions: CatalogFilterConfig[] | undefined) {
230
229
  if (!filtersWithOptions) return [];
231
230
  const parents: Set<number>[] = [];
232
231
  // use filter idx as id so we don't need users to provide unique ids
@@ -0,0 +1,138 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import { ArrowIcon } from '@theme/icons';
5
+ import { useOutsideClick } from '@theme/hooks';
6
+
7
+ export interface DropdownProps {
8
+ children: React.ReactNode;
9
+ items: JSX.Element[] | string[];
10
+ dataAttributes?: Record<string, string>;
11
+ className?: string;
12
+ withArrow?: boolean;
13
+ triggerEvent?: 'click' | 'hover';
14
+ onChange?: (value: React.ReactNode | string) => void;
15
+ }
16
+
17
+ export const Dropdown = ({
18
+ children,
19
+ className,
20
+ items,
21
+ withArrow,
22
+ triggerEvent = 'click',
23
+ onChange,
24
+ dataAttributes,
25
+ }: DropdownProps) => {
26
+ const dropdownRef = useRef<HTMLDivElement | null>(null);
27
+
28
+ const [isOpen, setIsOpen] = useState<boolean>(false);
29
+
30
+ const handleOpen = () => {
31
+ setIsOpen(true);
32
+ };
33
+
34
+ const handleClose = () => {
35
+ setIsOpen(false);
36
+ };
37
+
38
+ const handleToggle = () => {
39
+ setIsOpen(!isOpen);
40
+ };
41
+
42
+ const handleClick = (value: React.ReactNode | string) => {
43
+ setIsOpen(false);
44
+ onChange?.(value);
45
+ };
46
+
47
+ useOutsideClick(dropdownRef, handleClose);
48
+
49
+ return (
50
+ <DropdownContainer
51
+ {...dataAttributes}
52
+ data-testid="dropdown"
53
+ className={className}
54
+ ref={dropdownRef}
55
+ onPointerEnter={triggerEvent === 'hover' ? handleOpen : undefined}
56
+ onPointerLeave={handleClose}
57
+ onClick={triggerEvent === 'click' ? handleToggle : undefined}
58
+ >
59
+ <DropdownHeader>
60
+ {children}
61
+ {withArrow ? isOpen ? <ArrowIcon direction="up" /> : <ArrowIcon direction="down" /> : null}
62
+ </DropdownHeader>
63
+ {isOpen && (
64
+ <DropdownList>
65
+ {items.map((item, index) => (
66
+ <DropdownListItem
67
+ key={index}
68
+ onClick={() => handleClick(item)}
69
+ separator={(item as JSX.Element).props?.separator}
70
+ >
71
+ {item}
72
+ </DropdownListItem>
73
+ ))}
74
+ </DropdownList>
75
+ )}
76
+ </DropdownContainer>
77
+ );
78
+ };
79
+
80
+ const DropdownContainer = styled.div`
81
+ position: relative;
82
+ font-size: var(--dropdown-font-size);
83
+ font-weight: var(--dropdown-font-weight);
84
+ line-height: var(--dropdown-line-height);
85
+ border-radius: var(--dropdown-border-radius);
86
+ color: var(--dropdown-text-color);
87
+
88
+ a {
89
+ display: block;
90
+ text-decoration: none;
91
+ color: var(--dropdown-text-color);
92
+ }
93
+ `;
94
+
95
+ export const DropdownHeader = styled.div`
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ cursor: pointer;
100
+ `;
101
+
102
+ export const DropdownList = styled.ul`
103
+ position: absolute;
104
+ top: 100%;
105
+ left: 0;
106
+ margin: 0;
107
+ min-width: var(--dropdown-list-min-width);
108
+ max-width: var(--dropdown-list-max-width);
109
+ padding: var(--dropdown-list-padding);
110
+ background-color: var(--dropdown-list-background-color);
111
+ border-radius: var(--dropdown-list-border-radius);
112
+ box-shadow: var(--dropdown-list-box-shadow);
113
+ list-style-type: none;
114
+ cursor: pointer;
115
+ white-space: nowrap;
116
+ overflow: hidden;
117
+ z-index: 1;
118
+ `;
119
+
120
+ export const DropdownListItem = styled.li<{ separator?: boolean }>`
121
+ border-radius: var(--dropdown-list-item-border-radius);
122
+
123
+ & > * {
124
+ padding: var(--dropdown-list-item-vertical-padding) var(--dropdown-list-item-horizontal-padding);
125
+ }
126
+
127
+ :hover {
128
+ background-color: var(--dropdown-list-item-active-background-color);
129
+ }
130
+
131
+ ${({ separator }) =>
132
+ separator &&
133
+ css`
134
+ border-bottom: 1px solid var(--dropdown-list-item-separator-color);
135
+ border-bottom-left-radius: 0;
136
+ border-bottom-right-radius: 0;
137
+ `}
138
+ `;
@@ -0,0 +1 @@
1
+ export * from '@theme/components/Dropdown/Dropdown';
@@ -5,6 +5,7 @@ import { ArrowIcon } from '@theme/icons/ArrowIcon/ArrowIcon';
5
5
  import { MenuLinkItem } from '@theme/components/Menu/MenuLinkItem';
6
6
  import { MenuItemLabel } from '@theme/components/Menu/MenuItemLabel';
7
7
  import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
8
+ import { SidebarItemIcon } from '@theme/components/Sidebar/SidebarItemIcon';
8
9
  import type { ItemState } from '@theme/components/Sidebar/types';
9
10
  import { useTranslate } from '@portal/hooks';
10
11
 
@@ -50,6 +51,7 @@ export function MenuGroup({
50
51
  role={!item.link ? 'link' : 'none'}
51
52
  >
52
53
  {!!item.items.length && <MenuGroupArrow direction={isExpanded ? 'down' : 'right'} />}
54
+ {item.icon ? <SidebarItemIcon src={item.icon} /> : null}
53
55
  {translate(item.labelTranslationKey, item.label)}
54
56
  </MenuGroupLabel>
55
57
  </MenuLinkItem>
@@ -6,6 +6,7 @@ import { ExternalIcon } from '@theme/icons/ExternalIcon';
6
6
  import { MenuItemLabel } from '@theme/components/Menu/MenuItemLabel';
7
7
  import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
8
8
  import type { MenuItemProps } from '@theme/components/Sidebar/types';
9
+ import { SidebarItemIcon } from '@theme/components/Sidebar/SidebarItemIcon';
9
10
  import { useTranslate } from '@portal/hooks';
10
11
 
11
12
  export function MenuItem({ item, className }: MenuItemProps): JSX.Element {
@@ -14,6 +15,7 @@ export function MenuItem({ item, className }: MenuItemProps): JSX.Element {
14
15
  <Wrapper data-component-name="Sidebar/MenuItem" className={className}>
15
16
  <MenuLinkItem item={item}>
16
17
  <Label active={item.active}>
18
+ {item.icon ? <SidebarItemIcon src={item.icon} /> : null}
17
19
  {translate(item.labelTranslationKey, item.label)}
18
20
  {item.external ? <ExternalIcon dataComponentName="Sidebar/ExternalIcon" /> : null}
19
21
  </Label>
@@ -1,112 +1,67 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
3
  import styled from 'styled-components';
4
4
 
5
- import { DropdownWrapper } from '@theme/components/Navbar/NavbarDropdown';
6
- import { isPrimitive, isEmptyArray, withPathPrefix } from '@theme/utils';
7
- import type { ResolvedNavItem } from '@theme/types/portal';
5
+ import type { MobileMenuProps } from '@theme/components/Menu/types';
6
+ import { MenuType } from '@theme/components/Menu/constants';
8
7
  import { Menu, MenuItemLabel } from '@theme/components/Menu/index';
9
- import type { ItemState } from '@portal/Sidebar/types';
10
8
  import { MobileUserProfile } from '@theme/components/Navbar/MobileUserProfile';
11
- import { getPathnameForLocale } from '@portal/utils';
12
- import { useI18nConfig, useTranslate } from '@portal/hooks';
9
+ import { useCurrentProduct } from '@portal/hooks';
13
10
  import { ArrowIcon } from '@theme/icons';
11
+ import { Product } from '@theme/components/Product/Product';
12
+ import { useMobileMenuLevels } from '@theme/components/Menu/hooks/use-mobile-menu-levels';
13
+ import { useMobileMenuItems } from '@theme/components/Menu/hooks/use-mobile-menu-items';
14
14
 
15
- interface MobileMenuProps {
16
- navItems: ResolvedNavItem[];
17
- sidebarItems: ItemState[];
18
- className?: string;
19
- }
15
+ export function MobileMenu({ className }: MobileMenuProps): JSX.Element | null {
16
+ const location = useLocation();
17
+ const product = useCurrentProduct();
20
18
 
21
- enum MenuLevel {
22
- NAVBAR = 'NAVBAR',
23
- SIDEBAR = 'SIDEBAR',
24
- }
19
+ const menuLevels = useMobileMenuLevels();
25
20
 
26
- export function MobileMenu({
27
- navItems,
28
- sidebarItems,
29
- className,
30
- }: MobileMenuProps): JSX.Element | null {
31
- const [menuLevel, setMenuLevel] = useState(
32
- sidebarItems.length ? MenuLevel.SIDEBAR : MenuLevel.NAVBAR,
33
- );
34
- const { pathname, hash } = useLocation();
35
- const pathHash = pathname + hash;
36
- const { defaultLocale, currentLocale, locales } = useI18nConfig();
37
- const { translate } = useTranslate();
21
+ const [menuLevel, setMenuLevel] = useState(menuLevels.length - 1);
38
22
 
39
- if (isPrimitive(navItems) || isEmptyArray(navItems)) {
40
- return null;
41
- }
23
+ useEffect(() => {
24
+ setMenuLevel(menuLevels.length - 1);
25
+ }, [menuLevels, location]);
42
26
 
43
- const isItemActive = (item: ResolvedNavItem): boolean => {
44
- return (
45
- pathHash ===
46
- withPathPrefix(getPathnameForLocale(item.link || '/', defaultLocale, currentLocale, locales))
47
- );
48
- };
49
-
50
- const mapNavbarItems = (items: ResolvedNavItem[]): ItemState[] => {
51
- return items.map(
52
- (navItem) =>
53
- ({
54
- ...navItem,
55
- ...(navItem.items && { items: mapNavbarItems(navItem.items) }),
56
- ...('link' in navItem && { link: navItem.link || '/' }),
57
- active: 'link' in navItem && isItemActive(navItem),
58
- hasActiveSubItem: !!navItem.items?.find(isItemActive),
59
- } as ItemState),
60
- );
61
- };
62
-
63
- let menuItems: ItemState[];
64
- switch (menuLevel) {
65
- case MenuLevel.SIDEBAR:
66
- menuItems = sidebarItems;
67
- break;
68
- default:
69
- menuItems = mapNavbarItems(navItems);
70
- }
27
+ const menuType = menuLevels[menuLevel]?.type || MenuType.MAIN_MENU;
71
28
 
72
- const isMenuButtonVisible = menuLevel === MenuLevel.SIDEBAR;
73
- const isPreviousButtonVisible = menuLevel === MenuLevel.NAVBAR && !!sidebarItems.length;
74
- const isButtonsSectionVisible = isMenuButtonVisible || isPreviousButtonVisible;
29
+ const menuItems = useMobileMenuItems(menuType);
75
30
 
76
- const translationKeys = {
77
- mainMenu: 'theme.mobileMenu.mainMenu',
78
- previous: 'theme.mobileMenu.previous',
79
- };
31
+ const prevLevelName = menuLevels[menuLevel - 1]?.label;
32
+ const nextLevelName = menuLevels[menuLevel + 1]?.label;
33
+ const isButtonsSectionVisible = nextLevelName || prevLevelName;
80
34
 
81
35
  return (
82
36
  <MobileMenuWrapper data-component-name="Menu/MobileMenu" className={className}>
83
37
  <ScrollableArea>
84
38
  {isButtonsSectionVisible && (
85
- <ButtonsSection alignRight={isPreviousButtonVisible}>
86
- {isMenuButtonVisible && (
39
+ <ButtonsSection alignRight={!prevLevelName && !!nextLevelName}>
40
+ {prevLevelName && (
87
41
  <ControlButton
88
42
  data-component-name="Menu/ControlButton"
89
- onClick={() => setMenuLevel(MenuLevel.NAVBAR)}
43
+ onClick={() => setMenuLevel(menuLevel - 1)}
90
44
  >
91
45
  <ArrowIcon direction="left" weight="thin" />
92
- <ControlButtonLabel paddingDirection="left">
93
- {translate(translationKeys.mainMenu, 'Main Menu')}
94
- </ControlButtonLabel>
46
+ <ControlButtonLabel paddingDirection="left">{prevLevelName}</ControlButtonLabel>
95
47
  </ControlButton>
96
48
  )}
97
- {isPreviousButtonVisible && (
49
+ {nextLevelName && (
98
50
  <ControlButton
99
51
  data-component-name="Menu/ControlButton"
100
- onClick={() => setMenuLevel(MenuLevel.SIDEBAR)}
52
+ onClick={() => setMenuLevel(menuLevel + 1)}
101
53
  >
102
- <ControlButtonLabel paddingDirection="right">
103
- {translate(translationKeys.previous, 'Previous')}
104
- </ControlButtonLabel>
54
+ <ControlButtonLabel paddingDirection="right">{nextLevelName}</ControlButtonLabel>
105
55
  <ArrowIcon direction="right" weight="thin" />
106
56
  </ControlButton>
107
57
  )}
108
58
  </ButtonsSection>
109
59
  )}
60
+ {menuType === MenuType.PRODUCT && product ? (
61
+ <ProductNameWrapper>
62
+ <Product product={product} />
63
+ </ProductNameWrapper>
64
+ ) : null}
110
65
  <MenuWrapper>
111
66
  <Menu items={menuItems} isMobile />
112
67
  </MenuWrapper>
@@ -115,9 +70,8 @@ export function MobileMenu({
115
70
  </MobileMenuWrapper>
116
71
  );
117
72
  }
118
-
119
73
  const MobileMenuWrapper = styled.div`
120
- height: 100%;
74
+ height: calc(100vh - var(--navbar-height));
121
75
  width: 100%;
122
76
  position: fixed;
123
77
  z-index: var(--z-index-raised);
@@ -132,21 +86,6 @@ const MobileMenuWrapper = styled.div`
132
86
  ${({ theme }) => theme.mediaQueries.medium} {
133
87
  display: none;
134
88
  }
135
-
136
- ${DropdownWrapper} {
137
- & > div {
138
- text-align: center;
139
- padding: 10px 0;
140
- cursor: pointer;
141
- a {
142
- color: var(--navbar-text-color);
143
- text-decoration: none;
144
- }
145
- :hover {
146
- text-decoration: underline;
147
- }
148
- }
149
- }
150
89
  `;
151
90
 
152
91
  const ButtonsSection = styled.div<{ alignRight: boolean }>`
@@ -158,9 +97,8 @@ const ButtonsSection = styled.div<{ alignRight: boolean }>`
158
97
 
159
98
  const ScrollableArea = styled.div`
160
99
  padding-top: var(--mobile-menu-padding-top);
161
- flex: 1;
162
100
  overflow-y: scroll;
163
- margin-bottom: 140px;
101
+ flex-grow: 1;
164
102
  `;
165
103
 
166
104
  const MenuWrapper = styled.div`
@@ -207,3 +145,12 @@ const ControlButtonLabel = styled.span<{ paddingDirection: 'right' | 'left' }>`
207
145
  ${({ paddingDirection }) => paddingDirection === 'left' && 'padding-left: 8px'};
208
146
  ${({ paddingDirection }) => paddingDirection === 'right' && 'padding-right: 8px'};
209
147
  `;
148
+
149
+ const ProductNameWrapper = styled.div`
150
+ color: var(--mobile-menu-item-text-color);
151
+ border-radius: var(--mobile-menu-item-radius);
152
+ padding-left: var(--sidebar-item-padding-horizontal);
153
+ border: 1px solid rgba(0, 0, 0, 0.06);
154
+ padding: 8px 7px;
155
+ margin: 12px 8px 0 8px;
156
+ `;
@@ -7,6 +7,7 @@ import { SeparatorLine } from '@theme/components/Separator/SeparatorLine';
7
7
  import type { ItemState } from '@theme/components/Sidebar/types';
8
8
  import { useTranslate } from '@portal/hooks';
9
9
  import { ArrowIcon } from '@theme/icons';
10
+ import { SidebarItemIcon } from '@theme/components/Sidebar/SidebarItemIcon';
10
11
 
11
12
  interface MenuGroupProps {
12
13
  item: ItemState;
@@ -27,6 +28,7 @@ export function MobileMenuGroup({
27
28
  <MenuLinkItem item={item}>
28
29
  <MenuGroupLabel onClick={toggleExpanded} active={item.active} expanded={isExpanded}>
29
30
  <LabelContainer>
31
+ {item.icon ? <SidebarItemIcon src={item.icon} /> : null}
30
32
  <Label>{translate(item.labelTranslationKey, item.label)}</Label>
31
33
  </LabelContainer>
32
34
  {!!item.items.length && (
@@ -0,0 +1,5 @@
1
+ export enum MenuType {
2
+ MAIN_MENU = 'MAIN_MENU',
3
+ PRODUCT = 'PRODUCT',
4
+ PAGE = 'PAGE',
5
+ }