@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,56 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ConsentRuntime } from '../types/consent';
|
|
4
|
+
|
|
5
|
+
import { useThemeHooks } from './use-theme-hooks';
|
|
6
|
+
import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
|
|
7
|
+
|
|
8
|
+
type RealmOwnedRuntime = ConsentRuntime & {
|
|
9
|
+
mode: 'opt-in' | 'opt-out';
|
|
10
|
+
configuredMode: 'auto' | 'opt-in' | 'opt-out';
|
|
11
|
+
region: { source: 'timezone' | 'config' | 'none'; timeZone: string | null };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function isRealmOwned(runtime: ConsentRuntime): runtime is RealmOwnedRuntime {
|
|
15
|
+
return runtime.region.source !== 'provider';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getConsentRuntime(): ConsentRuntime | undefined {
|
|
19
|
+
if (typeof window === 'undefined') return undefined;
|
|
20
|
+
return (window as Window & { redocly?: { consent?: ConsentRuntime } }).redocly?.consent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Sends one consent.changed event per visitor choice in the modes Realm owns.
|
|
24
|
+
export function useConsentTelemetry(): void {
|
|
25
|
+
const { useTelemetry } = useThemeHooks();
|
|
26
|
+
const telemetry = useTelemetry();
|
|
27
|
+
const telemetryRef = useRef(telemetry);
|
|
28
|
+
telemetryRef.current = telemetry;
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const found = getConsentRuntime();
|
|
32
|
+
const runtime = found && isRealmOwned(found) ? found : null;
|
|
33
|
+
if (!runtime) return;
|
|
34
|
+
return runtime.on('change', (state) => {
|
|
35
|
+
const choice =
|
|
36
|
+
state.analytics && state.marketing
|
|
37
|
+
? 'accept'
|
|
38
|
+
: !state.analytics && !state.marketing
|
|
39
|
+
? 'reject'
|
|
40
|
+
: 'partial';
|
|
41
|
+
const { origin, pathname, search, hash } = window.location;
|
|
42
|
+
const uri = `${origin}${pathname}${search}${hash}`;
|
|
43
|
+
telemetryRef.current.sendConsentChangedMessage([
|
|
44
|
+
{
|
|
45
|
+
...getBaseDataAttributes('consent', 'consent'),
|
|
46
|
+
choice,
|
|
47
|
+
mode: runtime.mode,
|
|
48
|
+
configuredMode: runtime.configuredMode,
|
|
49
|
+
regionSource: runtime.region.source,
|
|
50
|
+
gpc: runtime.gpc,
|
|
51
|
+
},
|
|
52
|
+
getBaseDataAttributes(uri, 'page', uri),
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
}, []);
|
|
56
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -19,12 +19,28 @@ const fallbacks = {
|
|
|
19
19
|
useOtelTelemetry: () => ({ send: () => {} }),
|
|
20
20
|
useBreadcrumbs: () => ({ breadcrumbs: [], siblings: undefined }),
|
|
21
21
|
useBanner: () => ({ banner: undefined, dismissBanner: () => {} }),
|
|
22
|
+
useConsent: () => ({
|
|
23
|
+
enabled: false,
|
|
24
|
+
config: undefined,
|
|
25
|
+
mode: undefined,
|
|
26
|
+
state: undefined,
|
|
27
|
+
decided: false,
|
|
28
|
+
gpc: false,
|
|
29
|
+
isDialogOpen: false,
|
|
30
|
+
openDialog: () => {},
|
|
31
|
+
closeDialog: () => {},
|
|
32
|
+
accept: () => {},
|
|
33
|
+
reject: () => {},
|
|
34
|
+
save: () => {},
|
|
35
|
+
}),
|
|
22
36
|
useCodeHighlight: () => ({ highlight: (rawContent: string) => rawContent }),
|
|
23
37
|
useUserMenu: () => ({}),
|
|
24
38
|
usePageData: () => null,
|
|
25
39
|
usePageProps: () => ({}),
|
|
26
40
|
usePageSharedData: () => null,
|
|
27
41
|
useMcpData: () => ({ docs: { enabled: false } }),
|
|
42
|
+
useSearchFeatures: () => ({}),
|
|
43
|
+
useNavigationTabs: () => undefined,
|
|
28
44
|
};
|
|
29
45
|
|
|
30
46
|
export const useThemeHooks = () => {
|
|
@@ -28,6 +28,7 @@ import { code } from '@redocly/theme/components/CodeBlock/variables';
|
|
|
28
28
|
import { productPicker } from '@redocly/theme/components/Product/variables';
|
|
29
29
|
import { markdown } from '@redocly/theme/components/Markdown/variables';
|
|
30
30
|
import { banner } from '@redocly/theme/components/Banner/variables';
|
|
31
|
+
import { consent } from '@redocly/theme/components/Consent/variables';
|
|
31
32
|
import { markdownAccordion } from '@redocly/theme/markdoc/components/Accordion/variables';
|
|
32
33
|
import { markdownTabs } from '@redocly/theme/markdoc/components/Tabs/variables';
|
|
33
34
|
import { markdownNumberedList } from '@redocly/theme/markdoc/components/NumberedList/variables';
|
|
@@ -48,6 +49,8 @@ import { pageActions } from '@redocly/theme/components/PageActions/variables';
|
|
|
48
49
|
import { svgViewer } from '@redocly/theme/components/SvgViewer/variables';
|
|
49
50
|
import { toast } from '@redocly/theme/components/Toast/variables';
|
|
50
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';
|
|
51
54
|
|
|
52
55
|
import { activeBrandPaletteLight } from './palette';
|
|
53
56
|
import { darkMode } from './dark';
|
|
@@ -1124,8 +1127,8 @@ const error = css`
|
|
|
1124
1127
|
--detailed-error-overlay-z-index: var(--z-index-raised);
|
|
1125
1128
|
|
|
1126
1129
|
--detailed-error-modal-width: calc(100% - var(--spacing-lg) * 2);
|
|
1127
|
-
--detailed-error-modal-height: calc(100vh - var(--navbar-height) - var(--spacing-lg) * 2);
|
|
1128
|
-
--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));
|
|
1129
1132
|
--detailed-error-modal-border-radius: var(--border-radius-md);
|
|
1130
1133
|
--detailed-error-modal-bg-color: var(--bg-color-raised);
|
|
1131
1134
|
--detailed-error-modal-color: var(--text-color-primary);
|
|
@@ -1336,6 +1339,7 @@ export const styles = css<{ palette?: string }>`
|
|
|
1336
1339
|
${badges}
|
|
1337
1340
|
${banner}
|
|
1338
1341
|
${borders}
|
|
1342
|
+
${consent}
|
|
1339
1343
|
${breadcrumbs}
|
|
1340
1344
|
${button}
|
|
1341
1345
|
${aiAssistantButton}
|
|
@@ -1371,6 +1375,7 @@ export const styles = css<{ palette?: string }>`
|
|
|
1371
1375
|
${productPicker}
|
|
1372
1376
|
${responsePanelColors}
|
|
1373
1377
|
${search}
|
|
1378
|
+
${navigationTabs}
|
|
1374
1379
|
${select}
|
|
1375
1380
|
${sidebar}
|
|
1376
1381
|
${sizeAndSpace}
|
|
@@ -1411,6 +1416,13 @@ export const styles = css<{ palette?: string }>`
|
|
|
1411
1416
|
${darkMode};
|
|
1412
1417
|
}
|
|
1413
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
|
+
|
|
1414
1426
|
:root.notransition * {
|
|
1415
1427
|
transition: none !important;
|
|
1416
1428
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ConsentConfig } from '@redocly/config';
|
|
2
|
+
import type { TranslationKey } from './l10n';
|
|
3
|
+
|
|
4
|
+
export type ConsentCategoryId = 'necessary' | 'analytics' | 'marketing';
|
|
5
|
+
|
|
6
|
+
export type ConsentCategory = {
|
|
7
|
+
id: ConsentCategoryId;
|
|
8
|
+
locked: boolean;
|
|
9
|
+
titleTranslationKey: TranslationKey;
|
|
10
|
+
descriptionTranslationKey: TranslationKey;
|
|
11
|
+
titleDefault: string;
|
|
12
|
+
descriptionDefault: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ConsentState = { necessary: true; analytics: boolean; marketing: boolean };
|
|
16
|
+
|
|
17
|
+
export type ConsentChoice = { analytics?: boolean; marketing?: boolean };
|
|
18
|
+
|
|
19
|
+
export type ConsentProvider = 'onetrust' | 'cookiebot' | 'trustarc' | 'tcf';
|
|
20
|
+
|
|
21
|
+
export type ConsentRuntime = {
|
|
22
|
+
configuredMode: 'auto' | 'opt-in' | 'opt-out' | ConsentProvider;
|
|
23
|
+
mode: 'opt-in' | 'opt-out' | ConsentProvider;
|
|
24
|
+
state: ConsentState;
|
|
25
|
+
decided: boolean;
|
|
26
|
+
gpc: boolean;
|
|
27
|
+
dialogOpen: boolean;
|
|
28
|
+
region: { source: 'timezone' | 'config' | 'none' | 'provider'; timeZone: string | null };
|
|
29
|
+
accept: () => void;
|
|
30
|
+
reject: () => void;
|
|
31
|
+
save: (next: ConsentChoice) => void;
|
|
32
|
+
open: () => void;
|
|
33
|
+
close: () => void;
|
|
34
|
+
reload: () => void;
|
|
35
|
+
on: (event: 'change' | 'open' | 'close', fn: (state: ConsentState) => void) => () => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type UseConsentResult = {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
config: ConsentConfig | undefined;
|
|
41
|
+
mode: ConsentRuntime['mode'] | undefined;
|
|
42
|
+
state: ConsentState | undefined;
|
|
43
|
+
decided: boolean;
|
|
44
|
+
gpc: boolean;
|
|
45
|
+
isDialogOpen: boolean;
|
|
46
|
+
openDialog: () => void;
|
|
47
|
+
closeDialog: () => void;
|
|
48
|
+
accept: () => void;
|
|
49
|
+
reject: () => void;
|
|
50
|
+
save: (next: ConsentChoice) => void;
|
|
51
|
+
};
|
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,
|
|
@@ -40,6 +41,7 @@ import type {
|
|
|
40
41
|
import type { SubmitFeedbackParams } from './feedback';
|
|
41
42
|
import type { TFunction } from './l10n';
|
|
42
43
|
import type { BreadcrumbItem } from './breadcrumb';
|
|
44
|
+
import type { UseConsentResult } from './consent';
|
|
43
45
|
|
|
44
46
|
import type { AiSearchError } from '../constants/search';
|
|
45
47
|
|
|
@@ -90,6 +92,7 @@ export type ThemeHooks = {
|
|
|
90
92
|
banner: ResolvedBannerConfig | undefined;
|
|
91
93
|
dismissBanner: (content: string) => void;
|
|
92
94
|
};
|
|
95
|
+
useConsent: () => UseConsentResult;
|
|
93
96
|
useSearch: (
|
|
94
97
|
product?: string,
|
|
95
98
|
autoSearchDisabled?: boolean,
|
|
@@ -117,6 +120,9 @@ export type ThemeHooks = {
|
|
|
117
120
|
askAi?: boolean;
|
|
118
121
|
retrySearch: () => Promise<void>;
|
|
119
122
|
};
|
|
123
|
+
useSearchFeatures: () => {
|
|
124
|
+
askAi?: boolean;
|
|
125
|
+
};
|
|
120
126
|
useAiSearch: (options?: { filter?: SearchFilterItem[]; product?: string }) => {
|
|
121
127
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
122
128
|
isGeneratingResponse: boolean;
|
|
@@ -154,6 +160,7 @@ export type ThemeHooks = {
|
|
|
154
160
|
pushDrilldownState: (drilldownDetails: DrilldownMenuItemDetails) => void;
|
|
155
161
|
backLink?: { slug?: string; label?: string };
|
|
156
162
|
};
|
|
163
|
+
useNavigationTabs: () => NavigationTabsState | undefined;
|
|
157
164
|
useSidebarSiblingsData: () =>
|
|
158
165
|
| {
|
|
159
166
|
prevPage?: ResolvedNavItemWithLink;
|
package/src/core/types/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export type * from './breadcrumb';
|
|
2
2
|
export type * from './catalog';
|
|
3
|
+
export type * from './consent';
|
|
3
4
|
export type * from './l10n';
|
|
4
5
|
export type * from './mcp';
|
|
5
6
|
export type * from './feedback';
|
|
6
7
|
export type * from './hooks';
|
|
7
8
|
export type * from './markdown';
|
|
8
9
|
export type * from './navbar';
|
|
10
|
+
export type * from './navigation-tabs';
|
|
9
11
|
export * from './search';
|
|
10
12
|
export type * from './select';
|
|
11
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'
|
|
@@ -513,7 +516,28 @@ export type TranslationKey =
|
|
|
513
516
|
| 'diagram.zoomOut'
|
|
514
517
|
| 'diagram.reset'
|
|
515
518
|
| 'diagram.close'
|
|
516
|
-
| 'diagram.viewer'
|
|
519
|
+
| 'diagram.viewer'
|
|
520
|
+
| 'consent.title'
|
|
521
|
+
| 'consent.message'
|
|
522
|
+
| 'consent.privacyPolicy'
|
|
523
|
+
| 'consent.customize'
|
|
524
|
+
| 'consent.rejectAll'
|
|
525
|
+
| 'consent.acceptAll'
|
|
526
|
+
| 'consent.acceptAllCookies'
|
|
527
|
+
| 'consent.savePreferences'
|
|
528
|
+
| 'consent.dialog.title'
|
|
529
|
+
| 'consent.dialog.description'
|
|
530
|
+
| 'consent.dialog.close'
|
|
531
|
+
| 'consent.alwaysOn'
|
|
532
|
+
| 'consent.alwaysOff'
|
|
533
|
+
| 'consent.gpcNotice'
|
|
534
|
+
| 'consent.category.necessary.title'
|
|
535
|
+
| 'consent.category.necessary.description'
|
|
536
|
+
| 'consent.category.analytics.title'
|
|
537
|
+
| 'consent.category.analytics.description'
|
|
538
|
+
| 'consent.category.marketing.title'
|
|
539
|
+
| 'consent.category.marketing.description'
|
|
540
|
+
| 'consent.settingsLink';
|
|
517
541
|
|
|
518
542
|
export type Locale = { code: string; name: string };
|
|
519
543
|
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConsentConfig, RedoclyConfig } from '@redocly/config';
|
|
2
|
+
|
|
3
|
+
export type SettingsLinkPlacement = 'footer' | 'none';
|
|
4
|
+
|
|
5
|
+
export function resolveSettingsLinkPlacement(
|
|
6
|
+
consent: ConsentConfig | undefined,
|
|
7
|
+
footer: RedoclyConfig['footer'] | undefined,
|
|
8
|
+
): SettingsLinkPlacement {
|
|
9
|
+
const choice = consent?.settingsLink ?? 'auto';
|
|
10
|
+
if (choice !== 'auto') {
|
|
11
|
+
return choice;
|
|
12
|
+
}
|
|
13
|
+
const footerShown =
|
|
14
|
+
Boolean(footer) &&
|
|
15
|
+
!footer?.hide &&
|
|
16
|
+
(Boolean(footer?.copyrightText) || (Array.isArray(footer?.items) && footer.items.length > 0));
|
|
17
|
+
return footerShown ? 'footer' : 'none';
|
|
18
|
+
}
|
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';
|
|
@@ -49,3 +50,4 @@ export * from './tooltip-placement';
|
|
|
49
50
|
export * from './telemetry/get-base-data-attributes';
|
|
50
51
|
export * from './telemetry/generate-before-after-context';
|
|
51
52
|
export * from './RenderWithLink';
|
|
53
|
+
export * from './consent-settings-link-placement';
|
package/src/core/utils/mcp.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function slugifyMcpServerName(name: string): string {
|
|
|
39
39
|
return name
|
|
40
40
|
.toLowerCase()
|
|
41
41
|
.replace(/[^a-z0-9_-]+/g, '-')
|
|
42
|
-
.replace(
|
|
42
|
+
.replace(/^-+|(?<!-)-+$/g, '');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function generateClaudeCodeCommand(config: McpConnectionParams): string {
|
package/src/core/utils/menu.ts
CHANGED
|
@@ -3,8 +3,10 @@ import type { Location } from 'react-router';
|
|
|
3
3
|
|
|
4
4
|
import type { Locale, TFunction } from '../types/l10n';
|
|
5
5
|
import type { ItemState } from '../types/sidebar';
|
|
6
|
+
import type { NavigationTabRowItem, NavigationTabsState } from '../types/navigation-tabs';
|
|
6
7
|
import { MenuItemType } from '../constants/common';
|
|
7
|
-
import { getPathnameForLocale, withoutPathPrefix, withPathPrefix } from './urls';
|
|
8
|
+
import { addLeadingSlash, getPathnameForLocale, withoutPathPrefix, withPathPrefix } from './urls';
|
|
9
|
+
import { toNavigationTabsConfig } from './navigation-tabs';
|
|
8
10
|
|
|
9
11
|
const TRANSLATION_KEYS = {
|
|
10
12
|
version: 'mobileMenu.version',
|
|
@@ -24,10 +26,7 @@ export const mapNavbarItems = (
|
|
|
24
26
|
...(navItem.items && {
|
|
25
27
|
items: mapNavbarItems(navItem.items, defaultLocale, currentLocale, locales, location),
|
|
26
28
|
}),
|
|
27
|
-
|
|
28
|
-
active:
|
|
29
|
-
'link' in navItem &&
|
|
30
|
-
isItemActive(navItem, defaultLocale, currentLocale, locales, location),
|
|
29
|
+
active: isItemActive(navItem, defaultLocale, currentLocale, locales, location),
|
|
31
30
|
hasActiveSubItem: !!navItem.items?.find((item) =>
|
|
32
31
|
isItemActive(item, defaultLocale, currentLocale, locales, location),
|
|
33
32
|
),
|
|
@@ -42,10 +41,12 @@ export const isItemActive = (
|
|
|
42
41
|
locales: Locale[],
|
|
43
42
|
location: Location,
|
|
44
43
|
): boolean => {
|
|
44
|
+
if (!item.link) return false;
|
|
45
|
+
|
|
45
46
|
const pathHash = location.pathname + location.hash;
|
|
46
47
|
const link = item.languageInsensitive
|
|
47
|
-
? item.link
|
|
48
|
-
: getPathnameForLocale(item.link
|
|
48
|
+
? item.link
|
|
49
|
+
: getPathnameForLocale(item.link, defaultLocale, currentLocale, locales);
|
|
49
50
|
|
|
50
51
|
return pathHash === withPathPrefix(link);
|
|
51
52
|
};
|
|
@@ -71,7 +72,7 @@ export const buildLanguagesGroup = (
|
|
|
71
72
|
locale.code,
|
|
72
73
|
locales,
|
|
73
74
|
);
|
|
74
|
-
const newUrlWithLanguage = `${newLangPathname}${location.search}${location.hash}`;
|
|
75
|
+
const newUrlWithLanguage = `${addLeadingSlash(newLangPathname)}${location.search}${location.hash}`;
|
|
75
76
|
return {
|
|
76
77
|
type: 'link',
|
|
77
78
|
label: locale.name || locale.code || '',
|
|
@@ -112,8 +113,33 @@ export const buildVersionSection = (
|
|
|
112
113
|
];
|
|
113
114
|
};
|
|
114
115
|
|
|
116
|
+
export const buildNavigationTabsSelect = ({
|
|
117
|
+
navigationTabs,
|
|
118
|
+
}: {
|
|
119
|
+
navigationTabs: NavigationTabsState | undefined;
|
|
120
|
+
}): ItemState | undefined => {
|
|
121
|
+
const tabs = toNavigationTabsConfig({ state: navigationTabs })?.items;
|
|
122
|
+
|
|
123
|
+
if (!tabs) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const activeTab = tabs.find((tab) => tab.active);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
type: MenuItemType.Select,
|
|
131
|
+
label: activeTab?.label ?? 'Navigation tabs',
|
|
132
|
+
labelTranslationKey: activeTab ? activeTab.labelTranslationKey : 'navigationTabs.label',
|
|
133
|
+
items: tabs.map(toNavigationTabOption),
|
|
134
|
+
active: false,
|
|
135
|
+
hasActiveSubItem: !!activeTab,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
115
139
|
export function getMenuItemType(item: ItemState): MenuItemType {
|
|
116
|
-
if (item.type === MenuItemType.
|
|
140
|
+
if (item.type === MenuItemType.Select) {
|
|
141
|
+
return MenuItemType.Select;
|
|
142
|
+
} else if (item.type === MenuItemType.Separator) {
|
|
117
143
|
return MenuItemType.Separator;
|
|
118
144
|
} else if (item.menuStyle === 'drilldown') {
|
|
119
145
|
return MenuItemType.DrillDown;
|
|
@@ -123,3 +149,22 @@ export function getMenuItemType(item: ItemState): MenuItemType {
|
|
|
123
149
|
return MenuItemType.Operation;
|
|
124
150
|
} else return MenuItemType.Default;
|
|
125
151
|
}
|
|
152
|
+
|
|
153
|
+
function toNavigationTabOption(tab: NavigationTabRowItem): ItemState {
|
|
154
|
+
const landing =
|
|
155
|
+
tab.type === 'group' ? (tab.items.find((entry) => entry.active) ?? tab.items[0]) : tab;
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
type: tab.type,
|
|
159
|
+
label: tab.label,
|
|
160
|
+
labelTranslationKey: tab.labelTranslationKey,
|
|
161
|
+
icon: tab.icon,
|
|
162
|
+
srcSet: tab.srcSet,
|
|
163
|
+
link: landing?.link,
|
|
164
|
+
external: landing?.external,
|
|
165
|
+
target: landing?.target,
|
|
166
|
+
items: [],
|
|
167
|
+
active: !!tab.active,
|
|
168
|
+
hasActiveSubItem: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ThemeHooks } from '../types/hooks';
|
|
2
|
+
import type {
|
|
3
|
+
NavigationTab,
|
|
4
|
+
NavigationTabMenuEntry,
|
|
5
|
+
NavigationTabItem,
|
|
6
|
+
NavigationTabRowItem,
|
|
7
|
+
NavigationTabsConfig,
|
|
8
|
+
NavigationTabsState,
|
|
9
|
+
} from '../types/navigation-tabs';
|
|
10
|
+
|
|
11
|
+
import { getBaseDataAttributes } from './telemetry/get-base-data-attributes';
|
|
12
|
+
|
|
13
|
+
type NavbarItemClickedPayload = Parameters<
|
|
14
|
+
ReturnType<ThemeHooks['useTelemetry']>['sendNavbarMenuItemClickedMessage']
|
|
15
|
+
>[0];
|
|
16
|
+
|
|
17
|
+
export function toNavigationTabsConfig({
|
|
18
|
+
state,
|
|
19
|
+
}: {
|
|
20
|
+
state: NavigationTabsState | undefined;
|
|
21
|
+
}): NavigationTabsConfig | undefined {
|
|
22
|
+
const items = toNavigationTabEntries(state).map(
|
|
23
|
+
(entry): NavigationTabRowItem =>
|
|
24
|
+
entry.kind === 'menu'
|
|
25
|
+
? {
|
|
26
|
+
type: 'group',
|
|
27
|
+
label: entry.tab.label,
|
|
28
|
+
labelTranslationKey: entry.tab.labelTranslationKey,
|
|
29
|
+
icon: entry.tab.icon,
|
|
30
|
+
srcSet: entry.tab.srcSet,
|
|
31
|
+
items: entry.entries.map(({ active, ...link }) => toRowLink(link, active)),
|
|
32
|
+
active: entry.active,
|
|
33
|
+
}
|
|
34
|
+
: toRowLink(entry.link, entry.active),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return items.length ? { items } : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getNavbarItemClickedPayload(type: 'link' | 'group'): NavbarItemClickedPayload {
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
...getBaseDataAttributes<'navbarItem', 'navbar', 'urn:redocly:realm:ui:navbar:navbarItem'>(
|
|
44
|
+
'navbarItem',
|
|
45
|
+
'navbar',
|
|
46
|
+
),
|
|
47
|
+
type,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type NavigationTabLink = Omit<NavigationTabMenuEntry, 'tabId'>;
|
|
53
|
+
|
|
54
|
+
type NavigationTabEntry =
|
|
55
|
+
| { kind: 'link'; link: NavigationTabLink; active: boolean }
|
|
56
|
+
| {
|
|
57
|
+
kind: 'menu';
|
|
58
|
+
tab: NavigationTab;
|
|
59
|
+
entries: Array<NavigationTabLink & { active: boolean }>;
|
|
60
|
+
active: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function toNavigationTabEntries(state: NavigationTabsState | undefined): NavigationTabEntry[] {
|
|
64
|
+
if (!state?.tabs.length) return [];
|
|
65
|
+
|
|
66
|
+
return state.tabs.flatMap((tab): NavigationTabEntry[] => {
|
|
67
|
+
const active = state.activeTab?.tabId === tab.tabId;
|
|
68
|
+
|
|
69
|
+
if (tab.menu) {
|
|
70
|
+
const entries = tab.menu
|
|
71
|
+
.filter((menuEntry) => !!menuEntry.link)
|
|
72
|
+
.map(({ tabId, ...link }) => ({
|
|
73
|
+
...link,
|
|
74
|
+
active: active && tabId === state.activeMenuEntry?.tabId,
|
|
75
|
+
}));
|
|
76
|
+
return entries.length ? [{ kind: 'menu', tab, entries, active }] : [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!tab.link) return [];
|
|
80
|
+
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
kind: 'link',
|
|
84
|
+
link: {
|
|
85
|
+
label: tab.label,
|
|
86
|
+
labelTranslationKey: tab.labelTranslationKey,
|
|
87
|
+
link: tab.link,
|
|
88
|
+
icon: tab.icon,
|
|
89
|
+
srcSet: tab.srcSet,
|
|
90
|
+
},
|
|
91
|
+
active,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function toRowLink(link: NavigationTabLink, active: boolean): NavigationTabItem {
|
|
98
|
+
return { type: 'link', ...link, active };
|
|
99
|
+
}
|
package/src/core/utils/urls.ts
CHANGED
|
@@ -5,7 +5,9 @@ import { DEFAULT_LOCALE_PLACEHOLDER } from '../constants/common';
|
|
|
5
5
|
export function combineUrls(baseURL: string, ...relativeURLs: (string | undefined)[]) {
|
|
6
6
|
let res = baseURL;
|
|
7
7
|
for (let relativeURL of relativeURLs) {
|
|
8
|
-
res = relativeURL
|
|
8
|
+
res = relativeURL
|
|
9
|
+
? res.replace(/(?<![/\\])[/\\]+$/, '') + '/' + relativeURL.replace(/^[/\\]+/, '')
|
|
10
|
+
: res;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
return res;
|