@redocly/theme 0.70.0-next.3 → 0.70.0-next.4
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/components/Catalog/Catalog.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
- package/lib/components/Dropdown/Dropdown.js +3 -1
- package/lib/components/Markdown/Markdown.js +3 -2
- package/lib/components/Menu/Menu.js +4 -0
- package/lib/components/Menu/MenuSelect.d.ts +7 -0
- package/lib/components/Menu/MenuSelect.js +129 -0
- package/lib/components/Menu/variables.js +43 -4
- package/lib/components/Navbar/Navbar.js +44 -5
- package/lib/components/Navbar/variables.js +3 -0
- package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTab.js +93 -0
- package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
- package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
- package/lib/components/NavigationTabs/variables.d.ts +1 -0
- package/lib/components/NavigationTabs/variables.js +37 -0
- package/lib/components/Search/SearchTrigger.js +8 -2
- package/lib/components/Sidebar/Sidebar.js +2 -2
- package/lib/components/TableOfContent/TableOfContent.js +2 -2
- package/lib/core/constants/common.d.ts +1 -0
- package/lib/core/constants/common.js +1 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
- package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
- package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
- package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
- package/lib/core/hooks/use-theme-hooks.js +2 -0
- package/lib/core/styles/global.js +12 -2
- package/lib/core/types/hooks.d.ts +5 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/navigation-tabs.d.ts +49 -0
- package/lib/core/types/navigation-tabs.js +3 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/menu.d.ts +4 -0
- package/lib/core/utils/menu.js +42 -3
- package/lib/core/utils/navigation-tabs.d.ts +8 -0
- package/lib/core/utils/navigation-tabs.js +71 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +7 -1
- package/lib/layouts/PageLayout.js +1 -1
- package/lib/layouts/RootLayout.js +2 -2
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
- package/package.json +3 -3
- package/src/components/Catalog/Catalog.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
- package/src/components/Dropdown/Dropdown.tsx +4 -1
- package/src/components/Markdown/Markdown.tsx +3 -2
- package/src/components/Menu/Menu.tsx +5 -0
- package/src/components/Menu/MenuSelect.tsx +198 -0
- package/src/components/Menu/variables.ts +43 -4
- package/src/components/Navbar/Navbar.tsx +22 -5
- package/src/components/Navbar/variables.ts +3 -0
- package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
- package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
- package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
- package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
- package/src/components/NavigationTabs/variables.ts +34 -0
- package/src/components/Search/SearchTrigger.tsx +11 -2
- package/src/components/Sidebar/Sidebar.tsx +2 -2
- package/src/components/TableOfContent/TableOfContent.tsx +2 -2
- package/src/core/constants/common.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
- package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
- package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
- package/src/core/hooks/use-theme-hooks.ts +2 -0
- package/src/core/styles/global.ts +12 -2
- package/src/core/types/hooks.ts +5 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +3 -0
- package/src/core/types/navigation-tabs.ts +54 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/menu.ts +49 -3
- package/src/core/utils/navigation-tabs.ts +99 -0
- package/src/index.ts +6 -0
- package/src/layouts/PageLayout.tsx +1 -1
- package/src/layouts/RootLayout.tsx +2 -2
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +3 -3
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { JSX } from 'react';
|
|
5
|
+
import type { NavigationTabsConfig } from '@redocly/theme/core/types';
|
|
6
|
+
|
|
7
|
+
import { breakpoints } from '@redocly/theme/core/utils';
|
|
8
|
+
import { useScrollItemIntoView, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
9
|
+
import { NavigationTab } from '@redocly/theme/components/NavigationTabs/NavigationTab';
|
|
10
|
+
|
|
11
|
+
export type NavigationTabsProps = {
|
|
12
|
+
config?: NavigationTabsConfig;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function NavigationTabs({ config, className }: NavigationTabsProps): JSX.Element | null {
|
|
17
|
+
const { useTranslate } = useThemeHooks();
|
|
18
|
+
const { translate } = useTranslate();
|
|
19
|
+
const listRef = useRef<HTMLUListElement | null>(null);
|
|
20
|
+
|
|
21
|
+
const tabs = config?.items ?? [];
|
|
22
|
+
const activeIndex = tabs.findIndex((tab) => tab.active);
|
|
23
|
+
|
|
24
|
+
useScrollItemIntoView(listRef, activeIndex);
|
|
25
|
+
|
|
26
|
+
if (!tabs.length) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<NavigationTabsWrapper
|
|
32
|
+
data-component-name="NavigationTabs/NavigationTabs"
|
|
33
|
+
className={className}
|
|
34
|
+
aria-label={translate('navigationTabs.label', 'Navigation tabs')}
|
|
35
|
+
>
|
|
36
|
+
<NavigationTabsList ref={listRef}>
|
|
37
|
+
{tabs.map((tab, index) => (
|
|
38
|
+
<NavigationTab key={`${tab.label}_${index}`} tab={tab} />
|
|
39
|
+
))}
|
|
40
|
+
</NavigationTabsList>
|
|
41
|
+
</NavigationTabsWrapper>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const NavigationTabsWrapper = styled.nav`
|
|
46
|
+
display: none;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: var(--navigation-tabs-height);
|
|
50
|
+
max-width: var(--navbar-container-max-width);
|
|
51
|
+
padding: var(--navigation-tabs-padding);
|
|
52
|
+
background: var(--navigation-tabs-bg-color);
|
|
53
|
+
border-bottom: var(--navigation-tabs-bottom-border);
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
|
|
56
|
+
@media screen and (min-width: ${breakpoints.medium}) {
|
|
57
|
+
display: flex;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media screen and (min-width: ${breakpoints.max}) {
|
|
61
|
+
max-width: var(--container-max-width);
|
|
62
|
+
margin-left: auto;
|
|
63
|
+
margin-right: auto;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const NavigationTabsList = styled.ul`
|
|
68
|
+
list-style: none;
|
|
69
|
+
margin: 0 0 calc(-1 * var(--navigation-tabs-border-width));
|
|
70
|
+
padding: 0;
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-wrap: nowrap;
|
|
73
|
+
align-items: stretch;
|
|
74
|
+
gap: var(--navigation-tabs-gap);
|
|
75
|
+
min-width: 0;
|
|
76
|
+
overflow-x: auto;
|
|
77
|
+
scrollbar-width: none;
|
|
78
|
+
-ms-overflow-style: none;
|
|
79
|
+
|
|
80
|
+
&::-webkit-scrollbar {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const navigationTabs = css`
|
|
4
|
+
--navigation-tabs-height: 48px; // @presenter Spacing
|
|
5
|
+
--navigation-tabs-bg-color: transparent; // @presenter Color
|
|
6
|
+
--navigation-tabs-border-color: var(--border-color-secondary);
|
|
7
|
+
--navigation-tabs-border-width: 1px;
|
|
8
|
+
--navigation-tabs-bottom-border: var(--navigation-tabs-border-width) solid var(--navigation-tabs-border-color);
|
|
9
|
+
--navigation-tabs-padding-vertical: 0;
|
|
10
|
+
--navigation-tabs-padding-horizontal: var(--spacing-xl);
|
|
11
|
+
--navigation-tabs-padding: var(--navigation-tabs-padding-vertical) var(--navigation-tabs-padding-horizontal); // @presenter Spacing
|
|
12
|
+
--navigation-tabs-gap: var(--spacing-base); // @presenter Spacing
|
|
13
|
+
|
|
14
|
+
--navigation-tabs-item-padding-vertical: var(--spacing-xxs);
|
|
15
|
+
--navigation-tabs-item-padding-horizontal: 0;
|
|
16
|
+
--navigation-tabs-item-padding: var(--navigation-tabs-item-padding-vertical) var(--navigation-tabs-item-padding-horizontal);
|
|
17
|
+
--navigation-tabs-item-content-gap: var(--spacing-xs);
|
|
18
|
+
--navigation-tabs-item-font-size: var(--font-size-base);
|
|
19
|
+
--navigation-tabs-item-line-height: var(--line-height-base);
|
|
20
|
+
--navigation-tabs-item-font-weight: var(--font-weight-regular);
|
|
21
|
+
--navigation-tabs-item-font-weight-active: var(--font-weight-medium);
|
|
22
|
+
--navigation-tabs-item-text-color: var(--navbar-text-color); // @presenter Color
|
|
23
|
+
--navigation-tabs-item-text-color-hover: var(--text-color-primary); // @presenter Color
|
|
24
|
+
--navigation-tabs-item-text-color-active: var(--text-color-primary); // @presenter Color
|
|
25
|
+
--navigation-tabs-item-bottom-border: var(--navigation-tabs-border-width) solid transparent;
|
|
26
|
+
--navigation-tabs-item-bottom-border-hover: var(--navigation-tabs-border-width) solid var(--border-color-primary);
|
|
27
|
+
--navigation-tabs-item-bottom-border-active: var(--navigation-tabs-border-width) solid var(--border-color-inverse);
|
|
28
|
+
--navigation-tabs-item-border-color-focused: var(--button-border-color-focused);
|
|
29
|
+
--navigation-tabs-item-border-radius: var(--navbar-item-border-radius);
|
|
30
|
+
--navigation-tabs-item-icon-size: 1.25em; // @presenter Spacing
|
|
31
|
+
--navigation-tabs-item-external-icon-margin-left: 0;
|
|
32
|
+
--navigation-tabs-item-label-font-weight-active: var(--navigation-tabs-item-font-weight-active);
|
|
33
|
+
--navigation-tabs-item-transition: 150ms ease-in-out;
|
|
34
|
+
`;
|
|
@@ -31,7 +31,10 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
mainShortcutKey = mainShortcutKey?.toUpperCase();
|
|
34
|
-
|
|
34
|
+
// A page can be cached and served to every platform, so the server renders a hidden
|
|
35
|
+
// placeholder and the browser picks the label after hydration.
|
|
36
|
+
const [isMac, setIsMac] = React.useState<boolean | undefined>(undefined);
|
|
37
|
+
React.useEffect(() => setIsMac(getUserAgent().includes('Mac')), []);
|
|
35
38
|
|
|
36
39
|
return (
|
|
37
40
|
<SearchTriggerWrapper
|
|
@@ -53,7 +56,9 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
|
|
|
53
56
|
<SearchIcon />
|
|
54
57
|
{translate('search.navbar.label', 'Search')}
|
|
55
58
|
{mainShortcutKey === DEFAULT_KEYBOARD_SHORTCUTS ? (
|
|
56
|
-
<
|
|
59
|
+
<SearchTriggerShortcut $isPending={isMac === undefined}>
|
|
60
|
+
{isMac ? '⌘K' : 'Ctrl+K'}
|
|
61
|
+
</SearchTriggerShortcut>
|
|
57
62
|
) : (
|
|
58
63
|
<span>{mainShortcutKey}</span>
|
|
59
64
|
)}
|
|
@@ -80,6 +85,10 @@ const SearchTriggerButton = styled(Button)`
|
|
|
80
85
|
}
|
|
81
86
|
`;
|
|
82
87
|
|
|
88
|
+
const SearchTriggerShortcut = styled.span<{ $isPending: boolean }>`
|
|
89
|
+
visibility: ${({ $isPending }) => ($isPending ? 'hidden' : 'visible')};
|
|
90
|
+
`;
|
|
91
|
+
|
|
83
92
|
const SearchTriggerInput = styled.div`
|
|
84
93
|
display: none;
|
|
85
94
|
align-items: center;
|
|
@@ -84,8 +84,8 @@ const SidebarContent = styled.aside<{ $opened: boolean; $collapsed?: boolean }>`
|
|
|
84
84
|
flex-direction: column;
|
|
85
85
|
width: 100%;
|
|
86
86
|
-webkit-font-smoothing: antialiased;
|
|
87
|
-
top:
|
|
88
|
-
height: calc(100vh - var(--navbar-
|
|
87
|
+
top: var(--navbar-offset);
|
|
88
|
+
height: calc(100vh - var(--navbar-offset));
|
|
89
89
|
overflow-x: hidden;
|
|
90
90
|
${({ $opened }) => `
|
|
91
91
|
display: ${$opened ? 'flex' : 'none'}
|
|
@@ -101,8 +101,8 @@ const TableOfContentHeader = styled.div`
|
|
|
101
101
|
|
|
102
102
|
const TableOfContentItems = styled.div`
|
|
103
103
|
position: sticky;
|
|
104
|
-
top: calc(var(--navbar-height) + var(--toc-offset-top));
|
|
105
|
-
max-height: calc(100vh - var(--navbar-height) - var(--toc-offset-top));
|
|
104
|
+
top: calc(var(--navbar-stack-height) + var(--toc-offset-top));
|
|
105
|
+
max-height: calc(100vh - var(--navbar-stack-height) - var(--toc-offset-top));
|
|
106
106
|
overflow-y: auto;
|
|
107
107
|
width: var(--toc-width);
|
|
108
108
|
border-left: solid 1px var(--toc-border-color);
|
|
@@ -70,6 +70,7 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
70
70
|
items: [],
|
|
71
71
|
isLoading: false,
|
|
72
72
|
})),
|
|
73
|
+
useSearchFeatures: vi.fn(() => ({ askAi: false })),
|
|
73
74
|
useAiSearch: vi.fn(() => ({
|
|
74
75
|
askQuestion: vi.fn(),
|
|
75
76
|
isGeneratingResponse: false,
|
|
@@ -117,6 +118,7 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
117
118
|
pushDrilldownState: vi.fn(),
|
|
118
119
|
}),
|
|
119
120
|
useSidebarSiblingsData: vi.fn(),
|
|
121
|
+
useNavigationTabs: vi.fn(),
|
|
120
122
|
useSubmitFeedback: vi.fn(() => ({
|
|
121
123
|
submitFeedback: vi.fn(),
|
|
122
124
|
})),
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
1
2
|
import { useLocation } from 'react-router';
|
|
2
3
|
|
|
3
|
-
import type { ResolvedNavItem } from '@redocly/config';
|
|
4
|
+
import type { ResolvedNavItem, Version } from '@redocly/config';
|
|
4
5
|
|
|
5
6
|
import { useThemeHooks } from '../use-theme-hooks';
|
|
6
7
|
import { useThemeConfig } from '../use-theme-config';
|
|
7
8
|
import { MobileMenuType } from '../../constants/common';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildLanguagesGroup,
|
|
11
|
+
buildNavigationTabsSelect,
|
|
12
|
+
buildVersionSection,
|
|
13
|
+
mapNavbarItems,
|
|
14
|
+
} from '../../utils/menu';
|
|
9
15
|
|
|
10
16
|
const MENU_SEPARATOR: ResolvedNavItem = {
|
|
11
17
|
type: 'separator',
|
|
12
18
|
separatorLine: true,
|
|
13
19
|
};
|
|
14
20
|
|
|
21
|
+
const NO_VERSIONS: Version[] = [];
|
|
22
|
+
const NO_NAV_ITEMS: ResolvedNavItem[] = [];
|
|
23
|
+
|
|
15
24
|
export const useMobileMenuItems = (menuType: MobileMenuType) => {
|
|
16
|
-
const {
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
const {
|
|
26
|
+
useTranslate,
|
|
27
|
+
useL10nConfig,
|
|
28
|
+
useCurrentProduct,
|
|
29
|
+
usePageVersions,
|
|
30
|
+
useSidebarItems,
|
|
31
|
+
useNavigationTabs,
|
|
32
|
+
} = useThemeHooks();
|
|
33
|
+
const versions = usePageVersions()?.versions ?? NO_VERSIONS;
|
|
19
34
|
const { defaultLocale, currentLocale, locales } = useL10nConfig();
|
|
20
35
|
const { translate } = useTranslate();
|
|
21
36
|
const activeVersion = versions.find((version) => version.active);
|
|
@@ -25,6 +40,7 @@ export const useMobileMenuItems = (menuType: MobileMenuType) => {
|
|
|
25
40
|
popDrilldownState,
|
|
26
41
|
backLink,
|
|
27
42
|
} = useSidebarItems(activeVersion);
|
|
43
|
+
const navigationTabs = useNavigationTabs();
|
|
28
44
|
const location = useLocation();
|
|
29
45
|
|
|
30
46
|
const currentProduct = useCurrentProduct();
|
|
@@ -33,74 +49,93 @@ export const useMobileMenuItems = (menuType: MobileMenuType) => {
|
|
|
33
49
|
const productThemeConfig = useThemeConfig();
|
|
34
50
|
const baseThemeConfig = useThemeConfig(false);
|
|
35
51
|
|
|
36
|
-
const baseNavItems = (baseThemeConfig.navbar?.items
|
|
52
|
+
const baseNavItems = (baseThemeConfig.navbar?.items ?? NO_NAV_ITEMS) as ResolvedNavItem[];
|
|
37
53
|
const productNavItems = (
|
|
38
|
-
hasProductNavbarOverride ? productThemeConfig.navbar?.items
|
|
54
|
+
hasProductNavbarOverride ? (productThemeConfig.navbar?.items ?? NO_NAV_ITEMS) : NO_NAV_ITEMS
|
|
39
55
|
) as ResolvedNavItem[];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
menuItems.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
menuItems.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
(product) => ({
|
|
56
|
+
const products = baseThemeConfig.products;
|
|
57
|
+
|
|
58
|
+
const items = useMemo(() => {
|
|
59
|
+
const menuItems: ResolvedNavItem[] = [];
|
|
60
|
+
const navigationTabsSelect =
|
|
61
|
+
menuType === MobileMenuType.PAGE ? buildNavigationTabsSelect({ navigationTabs }) : undefined;
|
|
62
|
+
|
|
63
|
+
if (menuType === MobileMenuType.PAGE) {
|
|
64
|
+
if (activeVersion) {
|
|
65
|
+
menuItems.push(...buildVersionSection(translate, versions, activeVersion));
|
|
66
|
+
}
|
|
67
|
+
if (menuItems.length && sidebarItems.length) {
|
|
68
|
+
menuItems.push(MENU_SEPARATOR);
|
|
69
|
+
}
|
|
70
|
+
menuItems.push(...(sidebarItems as ResolvedNavItem[]));
|
|
71
|
+
} else if (menuType === MobileMenuType.PRODUCT) {
|
|
72
|
+
menuItems.push(...productNavItems);
|
|
73
|
+
|
|
74
|
+
if (menuItems.length && baseNavItems.length) {
|
|
75
|
+
menuItems.push(MENU_SEPARATOR);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (baseNavItems.length) {
|
|
79
|
+
menuItems.push(...baseNavItems);
|
|
80
|
+
}
|
|
81
|
+
} else if (menuType === MobileMenuType.MAIN_MENU) {
|
|
82
|
+
const productMenuItems: ResolvedNavItem[] = Object.values(products || {}).map((product) => ({
|
|
67
83
|
label: product.name,
|
|
68
84
|
link: product.link,
|
|
69
85
|
icon: product.icon,
|
|
70
86
|
type: 'link',
|
|
71
|
-
})
|
|
72
|
-
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
if (productMenuItems.length) {
|
|
90
|
+
menuItems.push({
|
|
91
|
+
type: 'separator',
|
|
92
|
+
label: translate('mobileMenu.products', 'Products'),
|
|
93
|
+
});
|
|
73
94
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
type: 'separator',
|
|
77
|
-
label: translate('mobileMenu.products', 'Products'),
|
|
78
|
-
});
|
|
95
|
+
menuItems.push(...productMenuItems);
|
|
96
|
+
}
|
|
79
97
|
|
|
80
|
-
menuItems.
|
|
98
|
+
if (menuItems.length && baseNavItems.length) {
|
|
99
|
+
menuItems.push(MENU_SEPARATOR);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (baseNavItems.length) {
|
|
103
|
+
menuItems.push(...baseNavItems);
|
|
104
|
+
}
|
|
81
105
|
}
|
|
82
106
|
|
|
83
|
-
|
|
107
|
+
const languagesMenu = buildLanguagesGroup(locales, defaultLocale, currentLocale);
|
|
108
|
+
|
|
109
|
+
if (menuItems.length && languagesMenu) {
|
|
84
110
|
menuItems.push(MENU_SEPARATOR);
|
|
85
111
|
}
|
|
86
112
|
|
|
87
|
-
if (
|
|
88
|
-
menuItems.push(
|
|
113
|
+
if (languagesMenu) {
|
|
114
|
+
menuItems.push(languagesMenu);
|
|
89
115
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const languagesMenu = buildLanguagesGroup(locales, defaultLocale, currentLocale);
|
|
93
|
-
|
|
94
|
-
if (menuItems.length && languagesMenu) {
|
|
95
|
-
menuItems.push(MENU_SEPARATOR);
|
|
96
|
-
}
|
|
97
116
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
return [
|
|
118
|
+
...(navigationTabsSelect ? [navigationTabsSelect] : []),
|
|
119
|
+
...mapNavbarItems(menuItems, defaultLocale, currentLocale, locales, location),
|
|
120
|
+
];
|
|
121
|
+
}, [
|
|
122
|
+
menuType,
|
|
123
|
+
navigationTabs,
|
|
124
|
+
activeVersion,
|
|
125
|
+
versions,
|
|
126
|
+
sidebarItems,
|
|
127
|
+
productNavItems,
|
|
128
|
+
baseNavItems,
|
|
129
|
+
products,
|
|
130
|
+
translate,
|
|
131
|
+
locales,
|
|
132
|
+
defaultLocale,
|
|
133
|
+
currentLocale,
|
|
134
|
+
location,
|
|
135
|
+
]);
|
|
101
136
|
|
|
102
137
|
return {
|
|
103
|
-
items
|
|
138
|
+
items,
|
|
104
139
|
popDrilldownState,
|
|
105
140
|
pushDrilldownState,
|
|
106
141
|
backLink,
|
|
@@ -8,27 +8,35 @@ import { useThemeConfig } from '../use-theme-config';
|
|
|
8
8
|
import { MobileMenuType } from '../../constants/common';
|
|
9
9
|
|
|
10
10
|
export const useMobileMenuLevels = () => {
|
|
11
|
-
const { useTranslate, useCurrentProduct, usePageVersions, useSidebarItems } =
|
|
11
|
+
const { useTranslate, useCurrentProduct, usePageVersions, useSidebarItems, useNavigationTabs } =
|
|
12
|
+
useThemeHooks();
|
|
12
13
|
const { versions = [] } = usePageVersions() || {};
|
|
13
14
|
const activeVersion = versions.find((version) => version?.active);
|
|
14
15
|
const { currentItems: sidebarItems } = useSidebarItems(activeVersion);
|
|
16
|
+
const navigationTabs = useNavigationTabs();
|
|
15
17
|
const { translate } = useTranslate();
|
|
16
18
|
const currentProduct = useCurrentProduct();
|
|
17
19
|
const productThemeConfig = useThemeConfig();
|
|
20
|
+
const baseThemeConfig = useThemeConfig(false);
|
|
18
21
|
const location = useLocation();
|
|
19
22
|
|
|
20
23
|
const hasProductNavbarOverride = currentProduct?.configOverride?.navbar;
|
|
21
24
|
const productNavItems = (
|
|
22
25
|
hasProductNavbarOverride ? productThemeConfig.navbar?.items || [] : []
|
|
23
26
|
) as ResolvedNavItem[];
|
|
27
|
+
const hasMainMenuItems =
|
|
28
|
+
Object.values(baseThemeConfig.products || {}).length > 0 ||
|
|
29
|
+
(baseThemeConfig.navbar?.items || []).length > 0;
|
|
24
30
|
|
|
25
31
|
const menuLevels = useMemo(() => {
|
|
26
|
-
const menuLevels = [
|
|
27
|
-
|
|
32
|
+
const menuLevels: Array<{ label: string; type: MobileMenuType }> = [];
|
|
33
|
+
|
|
34
|
+
if (hasMainMenuItems) {
|
|
35
|
+
menuLevels.push({
|
|
28
36
|
label: translate('mobileMenu.mainMenu', 'Main menu'),
|
|
29
37
|
type: MobileMenuType.MAIN_MENU,
|
|
30
|
-
}
|
|
31
|
-
|
|
38
|
+
});
|
|
39
|
+
}
|
|
32
40
|
|
|
33
41
|
if (currentProduct && productNavItems.length) {
|
|
34
42
|
menuLevels.push({
|
|
@@ -37,7 +45,7 @@ export const useMobileMenuLevels = () => {
|
|
|
37
45
|
});
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
if (sidebarItems.length || activeVersion) {
|
|
48
|
+
if (sidebarItems.length || activeVersion || navigationTabs?.tabs.length) {
|
|
41
49
|
menuLevels.push({
|
|
42
50
|
label: translate('mobileMenu.previous', 'Previous'),
|
|
43
51
|
type: MobileMenuType.PAGE,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
|
|
5
|
+
export function useScrollItemIntoView(
|
|
6
|
+
listRef: RefObject<HTMLElement | null>,
|
|
7
|
+
activeIndex: number,
|
|
8
|
+
): void {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const list = listRef.current;
|
|
11
|
+
const activeItem = activeIndex === -1 ? undefined : list?.children[activeIndex];
|
|
12
|
+
if (!list || !activeItem) return;
|
|
13
|
+
|
|
14
|
+
const listRect = list.getBoundingClientRect();
|
|
15
|
+
const itemRect = activeItem.getBoundingClientRect();
|
|
16
|
+
list.scrollLeft += itemRect.left - listRect.left - (listRect.width - itemRect.width) / 2;
|
|
17
|
+
}, [listRef, activeIndex]);
|
|
18
|
+
}
|
|
@@ -49,6 +49,8 @@ import { pageActions } from '@redocly/theme/components/PageActions/variables';
|
|
|
49
49
|
import { svgViewer } from '@redocly/theme/components/SvgViewer/variables';
|
|
50
50
|
import { toast } from '@redocly/theme/components/Toast/variables';
|
|
51
51
|
import { pageNavigation } from '@redocly/theme/components/PageNavigation/variables';
|
|
52
|
+
import { navigationTabs } from '@redocly/theme/components/NavigationTabs/variables';
|
|
53
|
+
import { breakpoints } from '@redocly/theme/core/utils';
|
|
52
54
|
|
|
53
55
|
import { activeBrandPaletteLight } from './palette';
|
|
54
56
|
import { darkMode } from './dark';
|
|
@@ -1125,8 +1127,8 @@ const error = css`
|
|
|
1125
1127
|
--detailed-error-overlay-z-index: var(--z-index-raised);
|
|
1126
1128
|
|
|
1127
1129
|
--detailed-error-modal-width: calc(100% - var(--spacing-lg) * 2);
|
|
1128
|
-
--detailed-error-modal-height: calc(100vh - var(--navbar-height) - var(--spacing-lg) * 2);
|
|
1129
|
-
--detailed-error-modal-top: calc(var(--spacing-lg) + var(--navbar-height));
|
|
1130
|
+
--detailed-error-modal-height: calc(100vh - var(--navbar-stack-height) - var(--spacing-lg) * 2);
|
|
1131
|
+
--detailed-error-modal-top: calc(var(--spacing-lg) + var(--navbar-stack-height));
|
|
1130
1132
|
--detailed-error-modal-border-radius: var(--border-radius-md);
|
|
1131
1133
|
--detailed-error-modal-bg-color: var(--bg-color-raised);
|
|
1132
1134
|
--detailed-error-modal-color: var(--text-color-primary);
|
|
@@ -1373,6 +1375,7 @@ export const styles = css<{ palette?: string }>`
|
|
|
1373
1375
|
${productPicker}
|
|
1374
1376
|
${responsePanelColors}
|
|
1375
1377
|
${search}
|
|
1378
|
+
${navigationTabs}
|
|
1376
1379
|
${select}
|
|
1377
1380
|
${sidebar}
|
|
1378
1381
|
${sizeAndSpace}
|
|
@@ -1413,6 +1416,13 @@ export const styles = css<{ palette?: string }>`
|
|
|
1413
1416
|
${darkMode};
|
|
1414
1417
|
}
|
|
1415
1418
|
|
|
1419
|
+
@media screen and (min-width: ${breakpoints.medium}) {
|
|
1420
|
+
:root:has([data-component-name='NavigationTabs/NavigationTabs']) {
|
|
1421
|
+
--navbar-stack-height: calc(var(--navbar-height) + var(--navigation-tabs-height, 48px));
|
|
1422
|
+
--navbar-row-bottom-border-color: transparent;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1416
1426
|
:root.notransition * {
|
|
1417
1427
|
transition: none !important;
|
|
1418
1428
|
}
|
package/src/core/types/hooks.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
} from './catalog';
|
|
29
29
|
import type { UserMenuData } from './user-menu';
|
|
30
30
|
import type { ItemState, DrilldownMenuItemDetails } from './sidebar';
|
|
31
|
+
import type { NavigationTabsState } from './navigation-tabs';
|
|
31
32
|
import type {
|
|
32
33
|
SearchItemData,
|
|
33
34
|
SearchFacet,
|
|
@@ -119,6 +120,9 @@ export type ThemeHooks = {
|
|
|
119
120
|
askAi?: boolean;
|
|
120
121
|
retrySearch: () => Promise<void>;
|
|
121
122
|
};
|
|
123
|
+
useSearchFeatures: () => {
|
|
124
|
+
askAi?: boolean;
|
|
125
|
+
};
|
|
122
126
|
useAiSearch: (options?: { filter?: SearchFilterItem[]; product?: string }) => {
|
|
123
127
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
124
128
|
isGeneratingResponse: boolean;
|
|
@@ -156,6 +160,7 @@ export type ThemeHooks = {
|
|
|
156
160
|
pushDrilldownState: (drilldownDetails: DrilldownMenuItemDetails) => void;
|
|
157
161
|
backLink?: { slug?: string; label?: string };
|
|
158
162
|
};
|
|
163
|
+
useNavigationTabs: () => NavigationTabsState | undefined;
|
|
159
164
|
useSidebarSiblingsData: () =>
|
|
160
165
|
| {
|
|
161
166
|
prevPage?: ResolvedNavItemWithLink;
|
package/src/core/types/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type * from './feedback';
|
|
|
7
7
|
export type * from './hooks';
|
|
8
8
|
export type * from './markdown';
|
|
9
9
|
export type * from './navbar';
|
|
10
|
+
export type * from './navigation-tabs';
|
|
10
11
|
export * from './search';
|
|
11
12
|
export type * from './select';
|
|
12
13
|
export type * from './sidebar';
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -222,6 +222,7 @@ export type TranslationKey =
|
|
|
222
222
|
| 'catalog.history.revision.current'
|
|
223
223
|
| 'catalog.history.revisions.showLess'
|
|
224
224
|
| 'catalog.history.revisions.showMore'
|
|
225
|
+
| 'navigationTabs.label'
|
|
225
226
|
| 'sidebar.menu.backLabel'
|
|
226
227
|
| 'sidebar.menu.backToLabel'
|
|
227
228
|
| 'sidebar.actions.show'
|
|
@@ -271,6 +272,7 @@ export type TranslationKey =
|
|
|
271
272
|
| 'mobileMenu.previous'
|
|
272
273
|
| 'mobileMenu.products'
|
|
273
274
|
| 'mobileMenu.version'
|
|
275
|
+
| 'navbar.label'
|
|
274
276
|
| 'navbar.products'
|
|
275
277
|
| 'page.nextButton'
|
|
276
278
|
| 'page.previousButton'
|
|
@@ -360,6 +362,7 @@ export type TranslationKey =
|
|
|
360
362
|
| 'openapi.hideExample'
|
|
361
363
|
| 'openapi.showExample'
|
|
362
364
|
| 'openapi.expandAll'
|
|
365
|
+
| 'openapi.deepLink.tooltip'
|
|
363
366
|
| 'openapi.collapseAll'
|
|
364
367
|
| 'openapi.viewSecurityDetails'
|
|
365
368
|
| 'openapi.noResponseExample'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type NavigationTabMenuEntry = {
|
|
2
|
+
tabId: string;
|
|
3
|
+
label: string;
|
|
4
|
+
labelTranslationKey?: string;
|
|
5
|
+
link: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
srcSet?: string;
|
|
8
|
+
external?: boolean;
|
|
9
|
+
target?: '_self' | '_blank';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type NavigationTab = {
|
|
13
|
+
tabId: string;
|
|
14
|
+
label: string;
|
|
15
|
+
labelTranslationKey?: string;
|
|
16
|
+
link?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
srcSet?: string;
|
|
19
|
+
menu?: NavigationTabMenuEntry[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type NavigationTabsState = {
|
|
23
|
+
tabs: NavigationTab[];
|
|
24
|
+
activeTab?: NavigationTab;
|
|
25
|
+
activeMenuEntry?: NavigationTabMenuEntry;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type NavigationTabItem = {
|
|
29
|
+
type: 'link';
|
|
30
|
+
label: string;
|
|
31
|
+
labelTranslationKey?: string;
|
|
32
|
+
link: string;
|
|
33
|
+
icon?: string;
|
|
34
|
+
srcSet?: string;
|
|
35
|
+
external?: boolean;
|
|
36
|
+
target?: '_self' | '_blank';
|
|
37
|
+
active?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type NavigationTabGroupItem = {
|
|
41
|
+
type: 'group';
|
|
42
|
+
label: string;
|
|
43
|
+
labelTranslationKey?: string;
|
|
44
|
+
icon?: string;
|
|
45
|
+
srcSet?: string;
|
|
46
|
+
items: NavigationTabItem[];
|
|
47
|
+
active?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type NavigationTabRowItem = NavigationTabItem | NavigationTabGroupItem;
|
|
51
|
+
|
|
52
|
+
export type NavigationTabsConfig = {
|
|
53
|
+
items: NavigationTabRowItem[];
|
|
54
|
+
};
|
package/src/core/utils/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from './type-guards';
|
|
|
17
17
|
export * from './dom';
|
|
18
18
|
export * from './toc';
|
|
19
19
|
export * from './menu';
|
|
20
|
+
export * from './navigation-tabs';
|
|
20
21
|
export * from './load-and-navigate';
|
|
21
22
|
export * from './get-scorecard-color-variable';
|
|
22
23
|
export * from './change-text-casing';
|