@redocly/theme 0.70.0-next.2 → 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/CodeBlock/CodeBlockControls.js +3 -5
- package/lib/components/Consent/Consent.d.ts +2 -0
- package/lib/components/Consent/Consent.js +17 -0
- package/lib/components/Consent/ConsentBanner.d.ts +2 -0
- package/lib/components/Consent/ConsentBanner.js +120 -0
- package/lib/components/Consent/ConsentDialog.d.ts +2 -0
- package/lib/components/Consent/ConsentDialog.js +128 -0
- package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
- package/lib/components/Consent/ConsentSettingsLink.js +35 -0
- package/lib/components/Consent/variables.d.ts +1 -0
- package/lib/components/Consent/variables.js +31 -0
- package/lib/components/Dropdown/Dropdown.js +3 -1
- package/lib/components/Footer/Footer.js +4 -1
- package/lib/components/JsonViewer/Helpers.d.ts +10 -2
- package/lib/components/JsonViewer/Helpers.js +29 -33
- package/lib/components/JsonViewer/JsonViewer.js +15 -18
- package/lib/components/Markdown/Markdown.js +3 -2
- package/lib/components/Markdown/styles/base-table.js +14 -1
- package/lib/components/Markdown/variables.js +1 -0
- 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/Product/utils.js +1 -1
- package/lib/components/Search/SearchAiMessage.js +1 -1
- package/lib/components/Search/SearchAiResponse.js +1 -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/constants/consent.d.ts +2 -0
- package/lib/core/constants/consent.js +30 -0
- package/lib/core/constants/index.d.ts +1 -0
- package/lib/core/constants/index.js +1 -0
- package/lib/core/hooks/index.d.ts +3 -0
- package/lib/core/hooks/index.js +3 -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-consent-dialog.d.ts +12 -0
- package/lib/core/hooks/use-consent-dialog.js +36 -0
- package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
- package/lib/core/hooks/use-consent-telemetry.js +42 -0
- 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 +16 -0
- package/lib/core/styles/global.js +54 -42
- package/lib/core/types/consent.d.ts +54 -0
- package/lib/core/types/consent.js +3 -0
- package/lib/core/types/hooks.d.ts +7 -0
- package/lib/core/types/index.d.ts +2 -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/consent-settings-link-placement.d.ts +3 -0
- package/lib/core/utils/consent-settings-link-placement.js +15 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.js +1 -1
- package/lib/core/utils/menu.d.ts +4 -0
- package/lib/core/utils/menu.js +48 -8
- package/lib/core/utils/navigation-tabs.d.ts +8 -0
- package/lib/core/utils/navigation-tabs.js +71 -0
- package/lib/core/utils/urls.js +3 -1
- package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
- package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +8 -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/CodeBlock/CodeBlockControls.tsx +4 -7
- package/src/components/Consent/Consent.tsx +18 -0
- package/src/components/Consent/ConsentBanner.tsx +110 -0
- package/src/components/Consent/ConsentDialog.tsx +210 -0
- package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
- package/src/components/Consent/variables.ts +28 -0
- package/src/components/Dropdown/Dropdown.tsx +4 -1
- package/src/components/Footer/Footer.tsx +2 -0
- package/src/components/JsonViewer/Helpers.tsx +46 -51
- package/src/components/JsonViewer/JsonViewer.tsx +23 -25
- package/src/components/Markdown/Markdown.tsx +3 -2
- package/src/components/Markdown/styles/base-table.ts +14 -1
- package/src/components/Markdown/variables.ts +1 -0
- 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/Product/utils.ts +1 -1
- package/src/components/Search/SearchAiMessage.tsx +1 -1
- package/src/components/Search/SearchAiResponse.tsx +1 -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/constants/consent.ts +29 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +17 -0
- package/src/core/hooks/index.ts +3 -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-consent-dialog.ts +51 -0
- package/src/core/hooks/use-consent-telemetry.ts +56 -0
- package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
- package/src/core/hooks/use-theme-hooks.ts +16 -0
- package/src/core/styles/global.ts +14 -2
- package/src/core/types/consent.ts +51 -0
- package/src/core/types/hooks.ts +7 -0
- package/src/core/types/index.ts +2 -0
- package/src/core/types/l10n.ts +25 -1
- package/src/core/types/navigation-tabs.ts +54 -0
- package/src/core/utils/consent-settings-link-placement.ts +18 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +1 -1
- package/src/core/utils/menu.ts +54 -9
- package/src/core/utils/navigation-tabs.ts +99 -0
- package/src/core/utils/urls.ts +3 -1
- package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
- package/src/index.ts +7 -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,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { JSX } from 'react';
|
|
5
|
+
import type { NavigationTabItem } from '@redocly/theme/core/types';
|
|
6
|
+
|
|
7
|
+
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
|
|
8
|
+
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
9
|
+
import { NavigationTabContent } from '@redocly/theme/components/NavigationTabs/NavigationTabContent';
|
|
10
|
+
|
|
11
|
+
export type NavigationTabMenuProps = {
|
|
12
|
+
items: NavigationTabItem[];
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function NavigationTabMenu({ items, className }: NavigationTabMenuProps): JSX.Element {
|
|
17
|
+
return (
|
|
18
|
+
<NavigationTabMenuWrapper
|
|
19
|
+
data-component-name="NavigationTabs/NavigationTabMenu"
|
|
20
|
+
className={className}
|
|
21
|
+
>
|
|
22
|
+
<DropdownMenu>
|
|
23
|
+
{items.map((item, index) => (
|
|
24
|
+
<DropdownMenuItem
|
|
25
|
+
key={`${item.label}_${index}`}
|
|
26
|
+
to={item.link}
|
|
27
|
+
external={item.external}
|
|
28
|
+
data-translation-key={item.labelTranslationKey}
|
|
29
|
+
active={item.active}
|
|
30
|
+
aria-current={item.active ? 'page' : undefined}
|
|
31
|
+
>
|
|
32
|
+
<NavigationTabContent item={item} />
|
|
33
|
+
</DropdownMenuItem>
|
|
34
|
+
))}
|
|
35
|
+
</DropdownMenu>
|
|
36
|
+
</NavigationTabMenuWrapper>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const NavigationTabMenuWrapper = styled.div``;
|
|
@@ -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
|
+
`;
|
|
@@ -347,7 +347,7 @@ const ResourceTagsWrapper = styled.div`
|
|
|
347
347
|
gap: var(--search-ai-resource-tags-gap);
|
|
348
348
|
`;
|
|
349
349
|
|
|
350
|
-
const ResourceTag = styled(Tag)`
|
|
350
|
+
const ResourceTag = styled(Tag).attrs({ textTransform: 'none' })`
|
|
351
351
|
.tag-default {
|
|
352
352
|
--tag-color: var(--search-ai-resource-tag-text-color);
|
|
353
353
|
max-width: 100%;
|
|
@@ -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);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ConsentCategory } from '../types/consent';
|
|
2
|
+
|
|
3
|
+
export const CONSENT_CATEGORIES: readonly ConsentCategory[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'necessary',
|
|
6
|
+
locked: true,
|
|
7
|
+
titleTranslationKey: 'consent.category.necessary.title',
|
|
8
|
+
descriptionTranslationKey: 'consent.category.necessary.description',
|
|
9
|
+
titleDefault: 'Necessary',
|
|
10
|
+
descriptionDefault:
|
|
11
|
+
'Needed for the site to work: your consent choice, your color mode, and anonymous usage data.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'analytics',
|
|
15
|
+
locked: false,
|
|
16
|
+
titleTranslationKey: 'consent.category.analytics.title',
|
|
17
|
+
descriptionTranslationKey: 'consent.category.analytics.description',
|
|
18
|
+
titleDefault: 'Analytics',
|
|
19
|
+
descriptionDefault: 'Helps us understand which pages are useful.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'marketing',
|
|
23
|
+
locked: false,
|
|
24
|
+
titleTranslationKey: 'consent.category.marketing.title',
|
|
25
|
+
descriptionTranslationKey: 'consent.category.marketing.description',
|
|
26
|
+
titleDefault: 'Marketing',
|
|
27
|
+
descriptionDefault: 'Lets us measure which campaigns bring visitors to this site.',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
@@ -19,12 +19,27 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
19
19
|
sendMcpConnectionSnippetCopiedMessage: vi.fn(),
|
|
20
20
|
sendPageActionsButtonClickedMessage: vi.fn(),
|
|
21
21
|
sendColorModeSwitchedMessage: vi.fn(),
|
|
22
|
+
sendConsentChangedMessage: vi.fn(),
|
|
22
23
|
})),
|
|
23
24
|
useBreadcrumbs: vi.fn().mockReturnValue({ breadcrumbs: [], siblings: undefined }),
|
|
24
25
|
useBanner: vi.fn(() => ({
|
|
25
26
|
banner: undefined,
|
|
26
27
|
dismissBanner: vi.fn(),
|
|
27
28
|
})),
|
|
29
|
+
useConsent: vi.fn(() => ({
|
|
30
|
+
enabled: false,
|
|
31
|
+
config: undefined,
|
|
32
|
+
mode: undefined,
|
|
33
|
+
state: undefined,
|
|
34
|
+
decided: false,
|
|
35
|
+
gpc: false,
|
|
36
|
+
isDialogOpen: false,
|
|
37
|
+
openDialog: vi.fn(),
|
|
38
|
+
closeDialog: vi.fn(),
|
|
39
|
+
accept: vi.fn(),
|
|
40
|
+
reject: vi.fn(),
|
|
41
|
+
save: vi.fn(),
|
|
42
|
+
})),
|
|
28
43
|
usePageSharedData: vi.fn().mockReturnValue({}),
|
|
29
44
|
useCatalogClassic: vi.fn(() => ({
|
|
30
45
|
groups: [],
|
|
@@ -55,6 +70,7 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
55
70
|
items: [],
|
|
56
71
|
isLoading: false,
|
|
57
72
|
})),
|
|
73
|
+
useSearchFeatures: vi.fn(() => ({ askAi: false })),
|
|
58
74
|
useAiSearch: vi.fn(() => ({
|
|
59
75
|
askQuestion: vi.fn(),
|
|
60
76
|
isGeneratingResponse: false,
|
|
@@ -102,6 +118,7 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
102
118
|
pushDrilldownState: vi.fn(),
|
|
103
119
|
}),
|
|
104
120
|
useSidebarSiblingsData: vi.fn(),
|
|
121
|
+
useNavigationTabs: vi.fn(),
|
|
105
122
|
useSubmitFeedback: vi.fn(() => ({
|
|
106
123
|
submitFeedback: vi.fn(),
|
|
107
124
|
})),
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export * from './menu/use-collapse';
|
|
|
22
22
|
export * from './feedback/use-report-dialog';
|
|
23
23
|
export * from './use-tabs';
|
|
24
24
|
export * from './use-focus-trap';
|
|
25
|
+
export * from './use-consent-dialog';
|
|
26
|
+
export * from './use-consent-telemetry';
|
|
25
27
|
export * from './use-language-picker';
|
|
26
28
|
export * from './use-product-picker';
|
|
27
29
|
export * from './search/use-search-dialog';
|
|
@@ -56,3 +58,4 @@ export * from './use-toast-logic';
|
|
|
56
58
|
export * from './use-banner-telemetry';
|
|
57
59
|
export * from './use-tooltip-fallback-placement';
|
|
58
60
|
export * from './use-anchor-positioning';
|
|
61
|
+
export * from './use-scroll-item-into-view';
|
|
@@ -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,51 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
import type { ConsentCategory, ConsentCategoryId, UseConsentResult } from '../types/consent';
|
|
5
|
+
|
|
6
|
+
import { CONSENT_CATEGORIES } from '../constants/consent';
|
|
7
|
+
import { useFocusTrap } from './use-focus-trap';
|
|
8
|
+
|
|
9
|
+
export type ConsentDraft = Record<Exclude<ConsentCategoryId, 'necessary'>, boolean>;
|
|
10
|
+
|
|
11
|
+
export type UseConsentDialogResult = {
|
|
12
|
+
draft: ConsentDraft;
|
|
13
|
+
setCategory: (id: keyof ConsentDraft, value: boolean) => void;
|
|
14
|
+
dialogRef: RefObject<HTMLDivElement | null>;
|
|
15
|
+
close: () => void;
|
|
16
|
+
save: () => void;
|
|
17
|
+
categories: readonly ConsentCategory[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function useConsentDialog(consent: UseConsentResult): UseConsentDialogResult {
|
|
21
|
+
const [draft, setDraft] = useState<ConsentDraft>(() => ({
|
|
22
|
+
analytics: consent.state?.analytics ?? consent.mode === 'opt-out',
|
|
23
|
+
marketing: (consent.state?.marketing ?? consent.mode === 'opt-out') && !consent.gpc,
|
|
24
|
+
}));
|
|
25
|
+
const dialogRef = useRef<HTMLDivElement | null>(null);
|
|
26
|
+
const close = consent.closeDialog;
|
|
27
|
+
const closeRef = useRef(close);
|
|
28
|
+
closeRef.current = close;
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const previous = document.activeElement as HTMLElement | null;
|
|
32
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
33
|
+
if (event.key === 'Escape') closeRef.current();
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener('keydown', onKeyDown);
|
|
36
|
+
return () => {
|
|
37
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
38
|
+
previous?.focus?.();
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
useFocusTrap(dialogRef);
|
|
43
|
+
|
|
44
|
+
const setCategory = (id: keyof ConsentDraft, value: boolean) => {
|
|
45
|
+
setDraft((current) => ({ ...current, [id]: value }));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const save = () => consent.save(draft);
|
|
49
|
+
|
|
50
|
+
return { draft, setCategory, dialogRef, close, save, categories: CONSENT_CATEGORIES };
|
|
51
|
+
}
|