@redocly/theme 0.70.0-next.3 → 0.70.0-next.5
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/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.d.ts +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/CatalogFilter/CatalogFilterCheckboxes.js +30 -11
- 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/Dropdown/DropdownMenu.d.ts +1 -0
- package/lib/components/Dropdown/DropdownMenu.js +1 -1
- package/lib/components/Feedback/Reasons.js +3 -2
- package/lib/components/Filter/FilterCheckboxes.js +30 -11
- package/lib/components/Image/Image.js +24 -4
- 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/Segmented/Segmented.js +23 -3
- package/lib/components/Select/Select.js +10 -3
- package/lib/components/Select/SelectInput.d.ts +1 -0
- package/lib/components/Select/SelectInput.js +3 -3
- 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 +2 -0
- package/lib/core/hooks/index.js +2 -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-tab-list.d.ts +31 -0
- package/lib/core/hooks/use-tab-list.js +49 -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/Dynamic.d.ts +8 -1
- package/lib/core/utils/Dynamic.js +34 -3
- 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/CatalogFilter/CatalogFilterCheckboxes.tsx +37 -22
- 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/Dropdown/DropdownMenu.tsx +7 -1
- package/src/components/Feedback/Reasons.tsx +3 -2
- package/src/components/Filter/FilterCheckboxes.tsx +33 -18
- package/src/components/Image/Image.tsx +26 -5
- 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/Segmented/Segmented.tsx +25 -16
- package/src/components/Select/Select.tsx +12 -6
- package/src/components/Select/SelectInput.tsx +8 -10
- 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 +3 -0
- package/src/core/hooks/index.ts +2 -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-tab-list.ts +63 -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/Dynamic.tsx +67 -35
- 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/icons/CheckboxIcon/CheckboxIcon.tsx +1 -0
- package/src/icons/ChevronSortIcon/ChevronSortIcon.tsx +1 -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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useCallback, useId, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { KeyboardEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
type UseTabListProps = {
|
|
6
|
+
count: number;
|
|
7
|
+
activeIndex: number;
|
|
8
|
+
onChange: (index: number) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for a WAI-ARIA tab list with automatic activation: only the active tab is a tab stop,
|
|
13
|
+
* and Left/Right/Home/End select and focus another tab.
|
|
14
|
+
*/
|
|
15
|
+
export function useTabList({ count, activeIndex, onChange }: UseTabListProps) {
|
|
16
|
+
const idPrefix = useId();
|
|
17
|
+
const focusableIndex = activeIndex >= 0 ? activeIndex : 0;
|
|
18
|
+
const tabRefs = useRef<(HTMLElement | null)[]>([]);
|
|
19
|
+
|
|
20
|
+
const select = useCallback(
|
|
21
|
+
(index: number) => {
|
|
22
|
+
onChange(index);
|
|
23
|
+
tabRefs.current[index]?.focus();
|
|
24
|
+
},
|
|
25
|
+
[onChange],
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const onKeyDown = useCallback(
|
|
29
|
+
(event: KeyboardEvent, index: number) => {
|
|
30
|
+
const target = (<Record<string, number>>{
|
|
31
|
+
ArrowRight: (index + 1) % count,
|
|
32
|
+
ArrowLeft: (index - 1 + count) % count,
|
|
33
|
+
Home: 0,
|
|
34
|
+
End: count - 1,
|
|
35
|
+
})[event.key];
|
|
36
|
+
if (target === undefined) return;
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
select(target);
|
|
39
|
+
},
|
|
40
|
+
[count, select],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const getTabProps = (index: number) => ({
|
|
44
|
+
id: `${idPrefix}-tab-${index}`,
|
|
45
|
+
role: 'tab',
|
|
46
|
+
'aria-selected': index === activeIndex,
|
|
47
|
+
'aria-controls': `${idPrefix}-panel-${index}`,
|
|
48
|
+
tabIndex: index === focusableIndex ? 0 : -1,
|
|
49
|
+
ref: (element: HTMLElement | null) => {
|
|
50
|
+
tabRefs.current[index] = element;
|
|
51
|
+
},
|
|
52
|
+
onClick: () => select(index),
|
|
53
|
+
onKeyDown: (event: KeyboardEvent) => onKeyDown(event, index),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const getTabPanelProps = (index: number) => ({
|
|
57
|
+
id: `${idPrefix}-panel-${index}`,
|
|
58
|
+
role: 'tabpanel',
|
|
59
|
+
'aria-labelledby': `${idPrefix}-tab-${index}`,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { tabListProps: { role: 'tablist' }, getTabProps, getTabPanelProps };
|
|
63
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -5,60 +5,86 @@ interface DynamicOptions<TProps extends Record<string, unknown>> {
|
|
|
5
5
|
loading?: React.ComponentType<TProps>;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A component from `dynamic()`. After `preload()` resolves, the component renders without a
|
|
10
|
+
* loading state. On the server, `preload()` does not load a client-only component (`ssr: false`).
|
|
11
|
+
*/
|
|
12
|
+
export type DynamicComponent<TProps> = React.ComponentType<TProps> & {
|
|
13
|
+
preload(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
|
|
8
16
|
export function dynamic<TProps extends Record<string, unknown>>(
|
|
9
17
|
factory: () => Promise<
|
|
10
18
|
React.ComponentType<TProps> | { default: React.ComponentType<TProps> } | Record<string, unknown>
|
|
11
19
|
>,
|
|
12
20
|
{ ssr = true, loading }: DynamicOptions<TProps> = {},
|
|
13
|
-
):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (mod && typeof mod === 'object' && 'default' in mod) {
|
|
25
|
-
const defaultExport = mod.default;
|
|
26
|
-
|
|
27
|
-
// Handle both function components and React component objects (e.g., memo/forwardRef)
|
|
28
|
-
if (typeof defaultExport === 'function') {
|
|
29
|
-
return { default: defaultExport as React.ComponentType<TProps> };
|
|
21
|
+
): DynamicComponent<TProps> {
|
|
22
|
+
let loaded: React.ComponentType<TProps> | undefined;
|
|
23
|
+
let pending: Promise<{ default: React.ComponentType<TProps> }> | undefined;
|
|
24
|
+
|
|
25
|
+
const load = () =>
|
|
26
|
+
(pending ??= factory()
|
|
27
|
+
.then((mod) => {
|
|
28
|
+
// Case 1: factory() => import('./SomeModule') - direct import
|
|
29
|
+
// Case 2: factory() => import('./SomeModule').then(mod => mod.default) - extract default
|
|
30
|
+
if (typeof mod === 'function') {
|
|
31
|
+
return { default: mod };
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
// Case 3: factory() => import('./SomeModule').then(mod => ({ default: mod.default })) - manual wrap
|
|
35
|
+
// Case 4: factory() => import('./SomeModule').then(mod => ({ default: mod.Named })) - manual wrap named
|
|
36
|
+
if (mod && typeof mod === 'object' && 'default' in mod) {
|
|
37
|
+
const defaultExport = mod.default;
|
|
38
|
+
|
|
39
|
+
// Handle both function components and React component objects (e.g., memo/forwardRef)
|
|
40
|
+
if (typeof defaultExport === 'function') {
|
|
41
|
+
return { default: defaultExport as React.ComponentType<TProps> };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (defaultExport && typeof defaultExport === 'object' && '$$typeof' in defaultExport) {
|
|
45
|
+
return { default: defaultExport as React.ComponentType<TProps> };
|
|
46
|
+
}
|
|
34
47
|
}
|
|
35
|
-
}
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
// Case 5: factory() => import('./SomeModule').then(mod => mod.Named) - find named export
|
|
50
|
+
if (mod && typeof mod === 'object') {
|
|
51
|
+
const modObj = mod as Record<string, unknown>;
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
for (const key in modObj) {
|
|
54
|
+
const value = modObj[key];
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
if (typeof value === 'function') {
|
|
57
|
+
return { default: value as React.ComponentType<TProps> };
|
|
58
|
+
}
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
if (value && typeof value === 'object' && '$$typeof' in value) {
|
|
61
|
+
return { default: value as React.ComponentType<TProps> };
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
|
-
}
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
throw new Error('Dynamic import did not return a valid React component');
|
|
67
|
+
})
|
|
68
|
+
.then(
|
|
69
|
+
(module) => {
|
|
70
|
+
loaded = module.default;
|
|
71
|
+
return module;
|
|
72
|
+
},
|
|
73
|
+
(error: unknown) => {
|
|
74
|
+
// A failed load is not kept, so the next preload tries again.
|
|
75
|
+
pending = undefined;
|
|
76
|
+
throw error;
|
|
77
|
+
},
|
|
78
|
+
));
|
|
79
|
+
|
|
80
|
+
const LazyComponent = React.lazy(load);
|
|
57
81
|
|
|
58
82
|
const LoadingComponent: React.ComponentType<TProps> = loading || (() => null);
|
|
59
83
|
|
|
60
|
-
|
|
84
|
+
const DynamicComponent = (props: TProps) => {
|
|
61
85
|
const [hasMounted, setHasMounted] = React.useState(false);
|
|
86
|
+
// Fixed per instance: a change of the element type would remount the component.
|
|
87
|
+
const [Component] = React.useState(() => loaded ?? LazyComponent);
|
|
62
88
|
|
|
63
89
|
React.useEffect(() => {
|
|
64
90
|
setHasMounted(true);
|
|
@@ -82,4 +108,10 @@ export function dynamic<TProps extends Record<string, unknown>>(
|
|
|
82
108
|
</React.Suspense>
|
|
83
109
|
);
|
|
84
110
|
};
|
|
111
|
+
|
|
112
|
+
return Object.assign(DynamicComponent, {
|
|
113
|
+
async preload() {
|
|
114
|
+
if (ssr || typeof window !== 'undefined') await load();
|
|
115
|
+
},
|
|
116
|
+
});
|
|
85
117
|
}
|
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';
|