@redocly/theme 0.60.0-next.6 → 0.60.0-next.8
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/Banner/Banner.d.ts +6 -0
- package/lib/components/Banner/Banner.js +172 -0
- package/lib/components/Banner/variables.d.ts +1 -0
- package/lib/components/Banner/variables.dark.d.ts +1 -0
- package/lib/components/Banner/variables.dark.js +12 -0
- package/lib/components/Banner/variables.js +45 -0
- package/lib/components/Buttons/AIAssistantButton.js +2 -1
- package/lib/components/LanguagePicker/LanguagePicker.js +1 -0
- package/lib/components/Navbar/Navbar.js +13 -5
- package/lib/components/Search/Search.js +6 -1
- package/lib/components/Search/SearchDialog.js +11 -5
- package/lib/core/contexts/SearchContext.d.ts +10 -0
- package/lib/core/contexts/SearchContext.js +56 -0
- package/lib/core/contexts/index.d.ts +1 -0
- package/lib/core/contexts/index.js +1 -0
- package/lib/core/hooks/search/use-search-dialog.js +4 -1
- package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
- package/lib/core/hooks/use-telemetry-fallback.js +2 -0
- package/lib/core/hooks/use-theme-hooks.js +1 -0
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/openapi/index.js +4 -1
- package/lib/core/styles/dark.js +2 -0
- package/lib/core/styles/global.js +32 -30
- package/lib/core/types/hooks.d.ts +7 -3
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -0
- package/package.json +7 -7
- package/src/components/Banner/Banner.tsx +179 -0
- package/src/components/Banner/variables.dark.ts +10 -0
- package/src/components/Banner/variables.ts +43 -0
- package/src/components/Buttons/AIAssistantButton.tsx +3 -2
- package/src/components/LanguagePicker/LanguagePicker.tsx +1 -0
- package/src/components/Navbar/Navbar.tsx +13 -5
- package/src/components/Search/Search.tsx +10 -1
- package/src/components/Search/SearchDialog.tsx +12 -5
- package/src/core/contexts/SearchContext.tsx +31 -0
- package/src/core/contexts/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +4 -0
- package/src/core/hooks/search/use-search-dialog.ts +4 -1
- package/src/core/hooks/use-telemetry-fallback.ts +2 -0
- package/src/core/hooks/use-theme-hooks.ts +1 -0
- package/src/core/openapi/index.ts +1 -0
- package/src/core/styles/dark.ts +2 -0
- package/src/core/styles/global.ts +2 -0
- package/src/core/types/hooks.ts +6 -5
- package/src/index.ts +2 -0
|
@@ -28,21 +28,22 @@ const variables_23 = require("../../components/Menu/variables");
|
|
|
28
28
|
const variables_24 = require("../../components/CodeBlock/variables");
|
|
29
29
|
const variables_25 = require("../../components/Product/variables");
|
|
30
30
|
const variables_26 = require("../../components/Markdown/variables");
|
|
31
|
-
const variables_27 = require("../../
|
|
32
|
-
const variables_28 = require("../../markdoc/components/
|
|
33
|
-
const variables_29 = require("../../components/
|
|
34
|
-
const variables_30 = require("../../components/
|
|
35
|
-
const variables_31 = require("../../components/
|
|
36
|
-
const variables_32 = require("../../components/
|
|
37
|
-
const variables_33 = require("../../components/
|
|
38
|
-
const variables_34 = require("../../components/
|
|
39
|
-
const variables_35 = require("../../components/
|
|
40
|
-
const variables_36 = require("../../components/
|
|
41
|
-
const variables_37 = require("../../components/
|
|
42
|
-
const variables_38 = require("../../
|
|
43
|
-
const variables_39 = require("../../markdoc/components/
|
|
44
|
-
const variables_40 = require("../../components/
|
|
45
|
-
const variables_41 = require("../../components/
|
|
31
|
+
const variables_27 = require("../../components/Banner/variables");
|
|
32
|
+
const variables_28 = require("../../markdoc/components/Tabs/variables");
|
|
33
|
+
const variables_29 = require("../../markdoc/components/Mermaid/variables");
|
|
34
|
+
const variables_30 = require("../../components/LastUpdated/variables");
|
|
35
|
+
const variables_31 = require("../../components/Logo/variables");
|
|
36
|
+
const variables_32 = require("../../components/StatusCode/variables");
|
|
37
|
+
const variables_33 = require("../../components/Segmented/variables");
|
|
38
|
+
const variables_34 = require("../../components/UserMenu/variables");
|
|
39
|
+
const variables_35 = require("../../components/Tags/variables");
|
|
40
|
+
const variables_36 = require("../../components/VersionPicker/variables");
|
|
41
|
+
const variables_37 = require("../../components/DatePicker/variables");
|
|
42
|
+
const variables_38 = require("../../components/Switch/variables");
|
|
43
|
+
const variables_39 = require("../../markdoc/components/Cards/variables");
|
|
44
|
+
const variables_40 = require("../../markdoc/components/CodeWalkthrough/variables");
|
|
45
|
+
const variables_41 = require("../../components/SkipContent/variables");
|
|
46
|
+
const variables_42 = require("../../components/PageActions/variables");
|
|
46
47
|
const dark_1 = require("./dark");
|
|
47
48
|
const themeColors = (0, styled_components_1.css) `
|
|
48
49
|
/* === Palette === */
|
|
@@ -1216,32 +1217,33 @@ exports.styles = (0, styled_components_1.css) `
|
|
|
1216
1217
|
${apiReferenceDocs}
|
|
1217
1218
|
${variables_11.apiReferencePanels}
|
|
1218
1219
|
${badges}
|
|
1220
|
+
${variables_27.banner}
|
|
1219
1221
|
${borders}
|
|
1220
1222
|
${variables_5.breadcrumbs}
|
|
1221
1223
|
${variables_19.button}
|
|
1222
1224
|
${variables_20.aiAssistantButton}
|
|
1223
1225
|
${variables_20.connectMCPButton}
|
|
1224
|
-
${
|
|
1226
|
+
${variables_39.cards}
|
|
1225
1227
|
${variables_8.catalog}
|
|
1226
1228
|
${variables_10.catalogClassic}
|
|
1227
1229
|
${variables_24.code}
|
|
1228
|
-
${
|
|
1230
|
+
${variables_40.codeWalkthrough}
|
|
1229
1231
|
${docsDropdown}
|
|
1230
1232
|
${variables_14.dropdown}
|
|
1231
1233
|
${error}
|
|
1232
1234
|
${variables_9.filter}
|
|
1233
1235
|
${variables_18.footer}
|
|
1234
1236
|
${headingsTypography}
|
|
1235
|
-
${
|
|
1237
|
+
${variables_35.httpTag}
|
|
1236
1238
|
${inputs}
|
|
1237
1239
|
${variables_1.languagePicker}
|
|
1238
|
-
${
|
|
1240
|
+
${variables_30.lastUpdated}
|
|
1239
1241
|
${links}
|
|
1240
1242
|
${loadProgressBar}
|
|
1241
|
-
${
|
|
1243
|
+
${variables_31.logo}
|
|
1242
1244
|
${variables_26.markdown}
|
|
1243
|
-
${
|
|
1244
|
-
${
|
|
1245
|
+
${variables_28.markdownTabs}
|
|
1246
|
+
${variables_29.mermaid}
|
|
1245
1247
|
${variables_23.menu}
|
|
1246
1248
|
${variables_23.mobileMenu}
|
|
1247
1249
|
${modal}
|
|
@@ -1259,23 +1261,23 @@ exports.styles = (0, styled_components_1.css) `
|
|
|
1259
1261
|
${variables_7.toc}
|
|
1260
1262
|
${variables_15.tooltip}
|
|
1261
1263
|
${typography}
|
|
1262
|
-
${
|
|
1263
|
-
${
|
|
1264
|
+
${variables_34.userMenu}
|
|
1265
|
+
${variables_36.versionPicker}
|
|
1264
1266
|
${zIndexDepth}
|
|
1265
1267
|
${scorecardColors}
|
|
1266
|
-
${
|
|
1268
|
+
${variables_32.statusCode}
|
|
1267
1269
|
${tab}
|
|
1268
1270
|
${icon}
|
|
1269
1271
|
${tree}
|
|
1270
|
-
${
|
|
1271
|
-
${
|
|
1272
|
+
${variables_33.segmented}
|
|
1273
|
+
${variables_38.switcher}
|
|
1272
1274
|
${variables_16.checkbox}
|
|
1273
1275
|
${variables_3.feedback}
|
|
1274
1276
|
${variables_2.scorecard}
|
|
1275
|
-
${
|
|
1277
|
+
${variables_37.datePicker}
|
|
1276
1278
|
${replay}
|
|
1277
|
-
${
|
|
1278
|
-
${
|
|
1279
|
+
${variables_41.skipContent}
|
|
1280
|
+
${variables_42.pageActions}
|
|
1279
1281
|
|
|
1280
1282
|
background-color: var(--bg-color);
|
|
1281
1283
|
color: var(--text-color-primary);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AsyncApiRealmUI } from '@redocly/realm-asyncapi-sdk';
|
|
2
|
-
import type { CatalogEntityConfig, PageData, PageProps, ResolvedNavItemWithLink, Version } from '@redocly/config';
|
|
2
|
+
import type { BannerConfig, CatalogEntityConfig, PageData, PageProps, ResolvedNavItemWithLink, Version } from '@redocly/config';
|
|
3
3
|
import type { ShikiTransformer } from '@shikijs/types';
|
|
4
4
|
import type { Callback, TFunction as TFunc } from 'i18next';
|
|
5
5
|
import type { To, Location, NavigateFunction } from 'react-router-dom';
|
|
@@ -60,7 +60,11 @@ export type ThemeHooks = {
|
|
|
60
60
|
breadcrumbs: BreadcrumbItem[];
|
|
61
61
|
currentItemSiblings?: BreadcrumbItem[];
|
|
62
62
|
};
|
|
63
|
-
|
|
63
|
+
useBanner: () => {
|
|
64
|
+
banner: BannerConfig | undefined;
|
|
65
|
+
dismissBanner: (content: string) => void;
|
|
66
|
+
};
|
|
67
|
+
useSearch: (product?: string, autoSearchDisabled?: boolean) => {
|
|
64
68
|
query: string;
|
|
65
69
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
|
66
70
|
filter: SearchFilterItem[];
|
|
@@ -78,7 +82,7 @@ export type ThemeHooks = {
|
|
|
78
82
|
};
|
|
79
83
|
useAiSearch: (options?: {
|
|
80
84
|
filter?: SearchFilterItem[];
|
|
81
|
-
}
|
|
85
|
+
}) => {
|
|
82
86
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
83
87
|
isGeneratingResponse: boolean;
|
|
84
88
|
question: string;
|
package/lib/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from './components/Footer/Footer';
|
|
|
44
44
|
export * from './components/Footer/FooterColumn';
|
|
45
45
|
export * from './components/Footer/FooterCopyright';
|
|
46
46
|
export * from './components/Footer/FooterItem';
|
|
47
|
+
export * from './components/Banner/Banner';
|
|
47
48
|
export * from './components/Typography/CompactTypography';
|
|
48
49
|
export * from './components/Typography/Emphasis';
|
|
49
50
|
export * from './components/Typography/H1';
|
package/lib/index.js
CHANGED
|
@@ -89,6 +89,8 @@ __exportStar(require("./components/Footer/Footer"), exports);
|
|
|
89
89
|
__exportStar(require("./components/Footer/FooterColumn"), exports);
|
|
90
90
|
__exportStar(require("./components/Footer/FooterCopyright"), exports);
|
|
91
91
|
__exportStar(require("./components/Footer/FooterItem"), exports);
|
|
92
|
+
/* Banner */
|
|
93
|
+
__exportStar(require("./components/Banner/Banner"), exports);
|
|
92
94
|
/* Typography */
|
|
93
95
|
__exportStar(require("./components/Typography/CompactTypography"), exports);
|
|
94
96
|
__exportStar(require("./components/Typography/Emphasis"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.60.0-next.
|
|
3
|
+
"version": "0.60.0-next.8",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@markdoc/markdoc": "0.5.2",
|
|
30
30
|
"lodash.debounce": "^4.0.8",
|
|
31
31
|
"lodash.throttle": "^4.1.1",
|
|
32
|
-
"react": "^19.1
|
|
33
|
-
"react-dom": "^19.1
|
|
32
|
+
"react": "^19.2.1",
|
|
33
|
+
"react-dom": "^19.2.1",
|
|
34
34
|
"react-router-dom": "^6.21.1",
|
|
35
35
|
"styled-components": "^4.1.1 || ^5.3.11 || ^6.0.0"
|
|
36
36
|
},
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@types/lodash.throttle": "4.1.9",
|
|
46
46
|
"@types/node": "22.18.13",
|
|
47
47
|
"@types/nprogress": "0.2.3",
|
|
48
|
-
"@types/react": "^19.
|
|
49
|
-
"@types/react-dom": "^19.
|
|
48
|
+
"@types/react": "^19.2.7",
|
|
49
|
+
"@types/react-dom": "^19.2.3",
|
|
50
50
|
"@types/styled-components": "5.1.34",
|
|
51
51
|
"@vitest/coverage-v8": "^4.0.10",
|
|
52
52
|
"@vitest/ui": "3.2.4",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "4.0.10",
|
|
64
64
|
"vitest-when": "0.6.2",
|
|
65
65
|
"webpack": "5.94.0",
|
|
66
|
-
"@redocly/realm-asyncapi-sdk": "0.6.0-next.
|
|
66
|
+
"@redocly/realm-asyncapi-sdk": "0.6.0-next.2"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@tanstack/react-query": "5.62.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"openapi-sampler": "1.6.2",
|
|
82
82
|
"react-calendar": "5.1.0",
|
|
83
83
|
"react-date-picker": "11.0.0",
|
|
84
|
-
"@redocly/config": "0.
|
|
84
|
+
"@redocly/config": "0.41.0"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useState } from 'react';
|
|
2
|
+
import styled, { css } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { BannerConfig } from '@redocly/config';
|
|
5
|
+
import type { JSX } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
8
|
+
import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
|
|
9
|
+
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
10
|
+
import { Button } from '@redocly/theme/components/Button/Button';
|
|
11
|
+
|
|
12
|
+
type BannerColor = 'info' | 'success' | 'warning' | 'error';
|
|
13
|
+
|
|
14
|
+
type BannerProps = {
|
|
15
|
+
className?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function setBannerHeight(height: number): void {
|
|
19
|
+
document.documentElement.style.setProperty('--banner-height', `${height}px`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Banner({ className }: BannerProps): JSX.Element | null {
|
|
23
|
+
const { useBanner, useMarkdownText } = useThemeHooks();
|
|
24
|
+
const { banner, dismissBanner } = useBanner();
|
|
25
|
+
const [displayBanner, setDisplayBanner] = useState<BannerConfig | undefined>(undefined);
|
|
26
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
27
|
+
const markdownContent = useMarkdownText(displayBanner?.content || '');
|
|
28
|
+
const bannerRef = useRef<HTMLDivElement>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (banner) {
|
|
32
|
+
setDisplayBanner(banner);
|
|
33
|
+
requestAnimationFrame(() => {
|
|
34
|
+
requestAnimationFrame(() => {
|
|
35
|
+
setIsVisible(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
setIsVisible(false);
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
setDisplayBanner(undefined);
|
|
42
|
+
}, 400);
|
|
43
|
+
return () => clearTimeout(timer);
|
|
44
|
+
}
|
|
45
|
+
}, [banner]);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!displayBanner) {
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
setBannerHeight(0);
|
|
51
|
+
}, 400);
|
|
52
|
+
return () => clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const bannerElement = bannerRef.current;
|
|
56
|
+
if (!bannerElement) return;
|
|
57
|
+
|
|
58
|
+
if (!isVisible) {
|
|
59
|
+
setBannerHeight(0);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const updateHeight = (): void => {
|
|
64
|
+
const height = bannerElement.getBoundingClientRect().height;
|
|
65
|
+
setBannerHeight(height);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
updateHeight();
|
|
69
|
+
|
|
70
|
+
const resizeObserver = new ResizeObserver(updateHeight);
|
|
71
|
+
resizeObserver.observe(bannerElement);
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
resizeObserver.disconnect();
|
|
75
|
+
};
|
|
76
|
+
}, [displayBanner, isVisible]);
|
|
77
|
+
|
|
78
|
+
if (!displayBanner) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const bannerColor =
|
|
83
|
+
((displayBanner as BannerConfig & { color?: BannerColor }).color as BannerColor) || 'info';
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<BannerWrapper
|
|
87
|
+
ref={bannerRef}
|
|
88
|
+
data-component-name="Banner/Banner"
|
|
89
|
+
className={className}
|
|
90
|
+
$color={bannerColor}
|
|
91
|
+
$isVisible={isVisible}
|
|
92
|
+
>
|
|
93
|
+
<BannerContent>
|
|
94
|
+
<Markdown compact>{markdownContent}</Markdown>
|
|
95
|
+
</BannerContent>
|
|
96
|
+
{displayBanner.dismissible && (
|
|
97
|
+
<DismissButton
|
|
98
|
+
variant="ghost"
|
|
99
|
+
size="var(--banner-button-size)"
|
|
100
|
+
icon={<CloseIcon color={`var(--banner-${bannerColor}-icon-color)`} size="16px" />}
|
|
101
|
+
onClick={() => dismissBanner(displayBanner.content)}
|
|
102
|
+
aria-label="Dismiss banner"
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</BannerWrapper>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const BannerContent = styled.div`
|
|
110
|
+
flex: 1;
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
|
|
115
|
+
p {
|
|
116
|
+
margin: 0;
|
|
117
|
+
color: var(--banner-text-color);
|
|
118
|
+
text-align: center;
|
|
119
|
+
|
|
120
|
+
a:not([role='button']) {
|
|
121
|
+
color: var(--banner-link-color);
|
|
122
|
+
text-decoration: var(--banner-link-decoration);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[data-component-name='Button/Button'] {
|
|
126
|
+
--button-font-size: var(--banner-button-font-size);
|
|
127
|
+
--button-border-radius: var(--banner-button-border-radius);
|
|
128
|
+
--button-padding: var(--banner-button-padding-inline);
|
|
129
|
+
--button-line-height: var(--banner-button-line-height);
|
|
130
|
+
--button-icon-size: var(--banner-button-icon-size);
|
|
131
|
+
--button-icon-padding: var(--banner-button-icon-padding);
|
|
132
|
+
--button-icon-left-padding: var(--banner-button-icon-left-padding);
|
|
133
|
+
--button-icon-right-padding: var(--banner-button-icon-right-padding);
|
|
134
|
+
margin: var(--banner-button-margin);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
p > * {
|
|
139
|
+
vertical-align: bottom;
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const BannerWrapper = styled.div<{ $color?: BannerColor; $isVisible?: boolean }>`
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: space-between;
|
|
147
|
+
padding: var(--banner-padding);
|
|
148
|
+
color: var(--banner-text-color);
|
|
149
|
+
min-height: var(--banner-min-height);
|
|
150
|
+
position: absolute;
|
|
151
|
+
top: 0;
|
|
152
|
+
left: 0;
|
|
153
|
+
right: 0;
|
|
154
|
+
width: 100%;
|
|
155
|
+
z-index: var(--z-index-overlay);
|
|
156
|
+
transform: ${({ $isVisible }) => ($isVisible ? 'translateY(0)' : 'translateY(-100%)')};
|
|
157
|
+
transition: transform 0.4s ease-out;
|
|
158
|
+
${({ $color }) =>
|
|
159
|
+
$color &&
|
|
160
|
+
css`
|
|
161
|
+
background-color: var(--banner-${$color}-bg-color);
|
|
162
|
+
|
|
163
|
+
${BannerContent} {
|
|
164
|
+
p {
|
|
165
|
+
color: var(--banner-${$color}-text-color);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
a:not([role='button']) {
|
|
169
|
+
color: var(--banner-${$color}-link-color);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`}
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
const DismissButton = styled(Button)`
|
|
176
|
+
width: var(--banner-button-size);
|
|
177
|
+
height: var(--banner-button-size);
|
|
178
|
+
padding: var(--banner-button-padding);
|
|
179
|
+
`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const banner = css`
|
|
4
|
+
/**
|
|
5
|
+
* @tokens Banner
|
|
6
|
+
*/
|
|
7
|
+
--banner-button-size: 22px;
|
|
8
|
+
--banner-button-padding: 3px; // @presenter Spacing
|
|
9
|
+
--banner-button-font-size: var(--font-size-base);
|
|
10
|
+
--banner-button-border-radius: var(--border-radius);
|
|
11
|
+
--banner-button-padding-inline: 1px var(--spacing-sm);
|
|
12
|
+
--banner-button-line-height: var(--line-height-base); // @presenter LineHeight
|
|
13
|
+
--banner-button-icon-size: 14px;
|
|
14
|
+
--banner-button-icon-padding: 5px; // @presenter Spacing
|
|
15
|
+
--banner-button-icon-left-padding: 1px var(--spacing-sm) 1px 10px; // @presenter Spacing
|
|
16
|
+
--banner-button-icon-right-padding: 1px 10px 1px var(--spacing-sm); // @presenter Spacing
|
|
17
|
+
--banner-button-margin: 0 var(--spacing-xs); // @presenter Spacing
|
|
18
|
+
--banner-padding: var(--spacing-xs); // @presenter Spacing
|
|
19
|
+
--banner-link-decoration: var(--link-decoration-hover);
|
|
20
|
+
--banner-min-height: 38px;
|
|
21
|
+
--banner-gap: var(--spacing-xxs); // @presenter Spacing
|
|
22
|
+
|
|
23
|
+
--banner-info-bg-color: var(--color-info-base); // @presenter Color
|
|
24
|
+
--banner-info-text-color: var(--color-static-white); // @presenter Color
|
|
25
|
+
--banner-info-icon-color: var(--color-static-white); // @presenter Color
|
|
26
|
+
--banner-info-link-color: var(--banner-info-text-color); // @presenter Color
|
|
27
|
+
|
|
28
|
+
--banner-success-bg-color: var(--color-success-base); // @presenter Color
|
|
29
|
+
--banner-success-text-color: var(--color-static-white); // @presenter Color
|
|
30
|
+
--banner-success-icon-color: var(--color-static-white); // @presenter Color
|
|
31
|
+
--banner-success-link-color: var(--banner-success-text-color); // @presenter Color
|
|
32
|
+
|
|
33
|
+
--banner-warning-bg-color: var(--color-warning-base); // @presenter Color
|
|
34
|
+
--banner-warning-text-color: var(--color-black); // @presenter Color
|
|
35
|
+
--banner-warning-icon-color: var(--color-black); // @presenter Color
|
|
36
|
+
--banner-warning-link-color: var(--banner-warning-text-color); // @presenter Color
|
|
37
|
+
|
|
38
|
+
--banner-error-bg-color: var(--color-error-base); // @presenter Color
|
|
39
|
+
--banner-error-text-color: var(--color-static-white); // @presenter Color
|
|
40
|
+
--banner-error-icon-color: var(--color-static-white); // @presenter Color
|
|
41
|
+
--banner-error-link-color: var(--banner-error-text-color); // @presenter Color
|
|
42
|
+
`;
|
|
43
|
+
|
|
@@ -8,6 +8,7 @@ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
|
8
8
|
import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon';
|
|
9
9
|
import { AiStarsGradientIcon } from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
|
|
10
10
|
import { RedoclyIcon } from '@redocly/theme/icons/RedoclyIcon/RedoclyIcon';
|
|
11
|
+
import { SearchSessionProvider } from '@redocly/theme/core/contexts';
|
|
11
12
|
|
|
12
13
|
type AIAssistantButtonIconType = 'chat' | 'sparkles' | 'redocly';
|
|
13
14
|
type AIAssistantButtonType = 'button' | 'icon';
|
|
@@ -86,7 +87,7 @@ export function AIAssistantButton() {
|
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
return (
|
|
89
|
-
|
|
90
|
+
<SearchSessionProvider>
|
|
90
91
|
<StyledAIAssistantButton
|
|
91
92
|
variant="outlined"
|
|
92
93
|
size="medium"
|
|
@@ -100,7 +101,7 @@ export function AIAssistantButton() {
|
|
|
100
101
|
</StyledAIAssistantButton>
|
|
101
102
|
|
|
102
103
|
{isOpen && <SearchDialog onClose={handleClose} initialMode="ai-dialog" />}
|
|
103
|
-
|
|
104
|
+
</SearchSessionProvider>
|
|
104
105
|
);
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -17,6 +17,7 @@ import { ProductPicker } from '@redocly/theme/components/Product/ProductPicker';
|
|
|
17
17
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
18
18
|
import { MenuIcon } from '@redocly/theme/icons/MenuIcon/MenuIcon';
|
|
19
19
|
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
20
|
+
import { Banner } from '@redocly/theme/components/Banner/Banner';
|
|
20
21
|
|
|
21
22
|
export type NavbarProps = {
|
|
22
23
|
className?: string;
|
|
@@ -43,6 +44,7 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
|
|
|
43
44
|
|
|
44
45
|
return (
|
|
45
46
|
<NavbarWrapper data-component-name="Navbar/Navbar" className={className}>
|
|
47
|
+
<Banner />
|
|
46
48
|
{isOpen && <MenuMobile hideUserProfile={!!hideUserMenu} />}
|
|
47
49
|
<NavbarRow>
|
|
48
50
|
{logo && <NavbarLogo config={logo} />}
|
|
@@ -76,16 +78,15 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
|
|
|
76
78
|
|
|
77
79
|
const NavbarWrapper = styled.nav`
|
|
78
80
|
--text-color: var(--navbar-text-color);
|
|
81
|
+
height: calc(var(--navbar-height) + var(--banner-height));
|
|
82
|
+
transition: height 0.4s ease-out;
|
|
79
83
|
|
|
80
84
|
position: sticky;
|
|
81
|
-
display: flex;
|
|
82
85
|
top: 0;
|
|
83
|
-
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
84
88
|
flex-shrink: 0;
|
|
85
|
-
align-items: center;
|
|
86
89
|
box-sizing: border-box;
|
|
87
|
-
padding: var(--navbar-padding);
|
|
88
|
-
border: var(--navbar-border);
|
|
89
90
|
font-size: var(--navbar-font-size);
|
|
90
91
|
font-family: var(--navbar-font-family);
|
|
91
92
|
z-index: var(--z-index-raised);
|
|
@@ -110,7 +111,14 @@ const NavbarRow = styled.div`
|
|
|
110
111
|
justify-content: space-between;
|
|
111
112
|
width: 100%;
|
|
112
113
|
gap: 8px;
|
|
114
|
+
height: var(--navbar-height);
|
|
113
115
|
max-width: var(--navbar-container-max-width);
|
|
116
|
+
padding: var(--navbar-padding);
|
|
117
|
+
border: var(--navbar-border);
|
|
118
|
+
background: var(--navbar-bg-color);
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
margin-top: var(--banner-height);
|
|
121
|
+
transition: margin-top 0.5s ease-out;
|
|
114
122
|
|
|
115
123
|
@media screen and (min-width: ${breakpoints.max}) {
|
|
116
124
|
max-width: var(--container-max-width);
|
|
@@ -6,12 +6,13 @@ import type { JSX } from 'react';
|
|
|
6
6
|
import { SearchTrigger } from '@redocly/theme/components/Search/SearchTrigger';
|
|
7
7
|
import { SearchDialog } from '@redocly/theme/components/Search/SearchDialog';
|
|
8
8
|
import { useSearchDialog } from '@redocly/theme/core/hooks';
|
|
9
|
+
import { SearchSessionProvider } from '@redocly/theme/core/contexts';
|
|
9
10
|
|
|
10
11
|
export type SearchProps = {
|
|
11
12
|
className?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
function SearchContent({ className }: SearchProps): JSX.Element {
|
|
15
16
|
const { isOpen, onOpen, onClose } = useSearchDialog();
|
|
16
17
|
|
|
17
18
|
return (
|
|
@@ -22,6 +23,14 @@ export function Search({ className }: SearchProps): JSX.Element {
|
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export function Search({ className }: SearchProps): JSX.Element {
|
|
27
|
+
return (
|
|
28
|
+
<SearchSessionProvider>
|
|
29
|
+
<SearchContent className={className} />
|
|
30
|
+
</SearchSessionProvider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
const SearchWrapper = styled.div`
|
|
26
35
|
margin-left: auto;
|
|
27
36
|
`;
|
|
@@ -24,6 +24,7 @@ import { SearchGroups } from '@redocly/theme/components/Search/SearchGroups';
|
|
|
24
24
|
import { Typography } from '@redocly/theme/components/Typography/Typography';
|
|
25
25
|
import { SpinnerLoader } from '@redocly/theme/components/Loaders/SpinnerLoader';
|
|
26
26
|
import { SearchAiDialog } from '@redocly/theme/components/Search/SearchAiDialog';
|
|
27
|
+
import { useSearchSession } from '@redocly/theme/core/contexts';
|
|
27
28
|
import { SettingsIcon } from '@redocly/theme/icons/SettingsIcon/SettingsIcon';
|
|
28
29
|
import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
29
30
|
import { ReturnKeyIcon } from '@redocly/theme/icons/ReturnKeyIcon/ReturnKeyIcon';
|
|
@@ -45,10 +46,10 @@ export function SearchDialog({
|
|
|
45
46
|
const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } =
|
|
46
47
|
useThemeHooks();
|
|
47
48
|
const telemetry = useTelemetry();
|
|
49
|
+
const { searchSessionId, refreshSearchSessionId } = useSearchSession();
|
|
48
50
|
const products = useProducts();
|
|
49
51
|
const currentProduct = useCurrentProduct();
|
|
50
52
|
const [product, setProduct] = useState(currentProduct);
|
|
51
|
-
const searchSessionId = `search-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
52
53
|
const [mode, setMode] = useState<'search' | 'ai-dialog'>(initialMode);
|
|
53
54
|
const autoSearchDisabled = mode !== 'search';
|
|
54
55
|
const {
|
|
@@ -63,7 +64,7 @@ export function SearchDialog({
|
|
|
63
64
|
advancedSearch,
|
|
64
65
|
askAi,
|
|
65
66
|
groupField,
|
|
66
|
-
} = useSearch(product?.name, autoSearchDisabled
|
|
67
|
+
} = useSearch(product?.name, autoSearchDisabled);
|
|
67
68
|
const {
|
|
68
69
|
isFilterOpen,
|
|
69
70
|
onFilterToggle,
|
|
@@ -73,7 +74,7 @@ export function SearchDialog({
|
|
|
73
74
|
onQuickFilterReset,
|
|
74
75
|
} = useSearchFilter(filter, setFilter);
|
|
75
76
|
const { addSearchHistoryItem } = useRecentSearches();
|
|
76
|
-
const aiSearch = useAiSearch({ filter }
|
|
77
|
+
const aiSearch = useAiSearch({ filter });
|
|
77
78
|
|
|
78
79
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
79
80
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
@@ -103,8 +104,11 @@ export function SearchDialog({
|
|
|
103
104
|
addSearchHistoryItem(value);
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
// Refresh the search session id so a new session starts on next open
|
|
108
|
+
refreshSearchSessionId();
|
|
109
|
+
|
|
106
110
|
onClose();
|
|
107
|
-
}, [addSearchHistoryItem, onClose]);
|
|
111
|
+
}, [addSearchHistoryItem, onClose, refreshSearchSessionId]);
|
|
108
112
|
|
|
109
113
|
useDialogHotKeys(modalRef, handleClose);
|
|
110
114
|
|
|
@@ -276,7 +280,10 @@ export function SearchDialog({
|
|
|
276
280
|
<Button
|
|
277
281
|
variant="secondary"
|
|
278
282
|
disabled={!aiSearch.conversation.length}
|
|
279
|
-
onClick={() =>
|
|
283
|
+
onClick={() => {
|
|
284
|
+
refreshSearchSessionId();
|
|
285
|
+
aiSearch.clearConversation();
|
|
286
|
+
}}
|
|
280
287
|
tabIndex={0}
|
|
281
288
|
icon={<EditIcon />}
|
|
282
289
|
>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export type SearchSessionContextValue = {
|
|
4
|
+
searchSessionId: string;
|
|
5
|
+
refreshSearchSessionId: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const SearchSessionContext = createContext<SearchSessionContextValue | null>(null);
|
|
9
|
+
|
|
10
|
+
export const SearchSessionProvider = ({ children }: { children: React.ReactNode }) => {
|
|
11
|
+
const [searchSessionId, setSearchSessionId] = useState(() => crypto.randomUUID());
|
|
12
|
+
|
|
13
|
+
const refreshSearchSessionId = useCallback(() => {
|
|
14
|
+
setSearchSessionId(crypto.randomUUID());
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const value = useMemo(
|
|
18
|
+
() => ({ searchSessionId, refreshSearchSessionId }),
|
|
19
|
+
[searchSessionId, refreshSearchSessionId],
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return <SearchSessionContext.Provider value={value}>{children}</SearchSessionContext.Provider>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function useSearchSession(): SearchSessionContextValue {
|
|
26
|
+
const contextValue = useContext(SearchSessionContext);
|
|
27
|
+
if (!contextValue) {
|
|
28
|
+
throw new Error('useSearchSession must be used within a SearchSessionProvider');
|
|
29
|
+
}
|
|
30
|
+
return contextValue;
|
|
31
|
+
}
|
|
@@ -17,6 +17,10 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
17
17
|
sendCodeSnippetReportedMessage: vi.fn(),
|
|
18
18
|
})),
|
|
19
19
|
useBreadcrumbs: vi.fn().mockReturnValue({ breadcrumbs: [], siblings: undefined }),
|
|
20
|
+
useBanner: vi.fn(() => ({
|
|
21
|
+
banner: undefined,
|
|
22
|
+
dismissBanner: vi.fn(),
|
|
23
|
+
})),
|
|
20
24
|
usePageSharedData: vi.fn().mockReturnValue({}),
|
|
21
25
|
useCatalogClassic: vi.fn(() => ({
|
|
22
26
|
groups: [],
|