@redocly/theme 0.51.0-next.2 → 0.51.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 +2 -26
- package/lib/components/Catalog/CatalogVirtualizedGroups.d.ts +11 -0
- package/lib/components/Catalog/CatalogVirtualizedGroups.js +125 -0
- package/lib/components/Search/SearchAiConversationInput.d.ts +8 -0
- package/lib/components/Search/SearchAiConversationInput.js +114 -0
- package/lib/components/Search/SearchAiDialog.d.ts +18 -0
- package/lib/components/Search/SearchAiDialog.js +165 -0
- package/lib/components/Search/SearchAiMessage.d.ts +12 -0
- package/lib/components/Search/SearchAiMessage.js +146 -0
- package/lib/components/Search/SearchAiResponse.d.ts +1 -0
- package/lib/components/Search/SearchAiResponse.js +39 -3
- package/lib/components/Search/SearchDialog.js +83 -25
- package/lib/components/Search/variables.js +112 -6
- package/lib/core/constants/search.d.ts +4 -0
- package/lib/core/constants/search.js +6 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +39 -10
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +13 -12
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/use-element-size.d.ts +7 -0
- package/lib/core/hooks/use-element-size.js +51 -0
- package/lib/core/types/hooks.d.ts +6 -4
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/search.d.ts +9 -0
- package/lib/icons/AiStarsIcon/AiStarsIcon.d.ts +9 -6
- package/lib/icons/AiStarsIcon/AiStarsIcon.js +38 -4
- package/lib/icons/ChatIcon/ChatIcon.d.ts +9 -0
- package/lib/icons/ChatIcon/ChatIcon.js +24 -0
- package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.d.ts +9 -0
- package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.js +22 -0
- package/lib/icons/DataRefineryIcon/DataRefineryIcon.d.ts +9 -0
- package/lib/icons/DataRefineryIcon/DataRefineryIcon.js +24 -0
- package/lib/icons/DraggableIcon/DraggableIcon.d.ts +9 -0
- package/lib/icons/DraggableIcon/DraggableIcon.js +27 -0
- package/lib/icons/FlowIcon/FlowIcon.d.ts +9 -0
- package/lib/icons/FlowIcon/FlowIcon.js +22 -0
- package/lib/icons/PlaylistIcon/PlaylistIcon.d.ts +9 -0
- package/lib/icons/PlaylistIcon/PlaylistIcon.js +24 -0
- package/lib/icons/SendIcon/SendIcon.d.ts +5 -0
- package/lib/icons/SendIcon/SendIcon.js +22 -0
- package/lib/icons/SettingsCogIcon/SettingsCogIcon.d.ts +9 -0
- package/lib/icons/SettingsCogIcon/SettingsCogIcon.js +25 -0
- package/lib/icons/TaskViewIcon/TaskViewIcon.d.ts +9 -0
- package/lib/icons/TaskViewIcon/TaskViewIcon.js +24 -0
- package/lib/icons/WarningAltFilled/WarningAltFilled.d.ts +9 -0
- package/lib/icons/WarningAltFilled/WarningAltFilled.js +23 -0
- package/lib/icons/WarningAltFilledIcon/WarningAltFilledIcon.d.ts +9 -0
- package/lib/icons/WarningAltFilledIcon/WarningAltFilledIcon.js +23 -0
- package/lib/icons/WorkflowAutomationIcon/WorkflowAutomationIcon.d.ts +9 -0
- package/lib/icons/WorkflowAutomationIcon/WorkflowAutomationIcon.js +24 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +11 -0
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +2 -28
- package/package.json +5 -4
- package/src/components/Catalog/Catalog.tsx +3 -37
- package/src/components/Catalog/CatalogVirtualizedGroups.tsx +152 -0
- package/src/components/Search/SearchAiConversationInput.tsx +133 -0
- package/src/components/Search/SearchAiDialog.tsx +238 -0
- package/src/components/Search/SearchAiMessage.tsx +209 -0
- package/src/components/Search/SearchAiResponse.tsx +59 -3
- package/src/components/Search/SearchDialog.tsx +148 -56
- package/src/components/Search/variables.ts +112 -6
- package/src/core/constants/search.ts +4 -0
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +51 -11
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +15 -12
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/use-element-size.ts +61 -0
- package/src/core/types/hooks.ts +15 -3
- package/src/core/types/l10n.ts +7 -0
- package/src/core/types/search.ts +10 -0
- package/src/icons/AiStarsIcon/AiStarsIcon.tsx +49 -14
- package/src/icons/ChatIcon/ChatIcon.tsx +35 -0
- package/src/icons/CheckboxFilledIcon/CheckboxFilledIcon.tsx +23 -0
- package/src/icons/DataRefineryIcon/DataRefineryIcon.tsx +34 -0
- package/src/icons/DraggableIcon/DraggableIcon.tsx +28 -0
- package/src/icons/FlowIcon/FlowIcon.tsx +26 -0
- package/src/icons/PlaylistIcon/PlaylistIcon.tsx +25 -0
- package/src/icons/SendIcon/SendIcon.tsx +33 -0
- package/src/icons/SettingsCogIcon/SettingsCogIcon.tsx +32 -0
- package/src/icons/TaskViewIcon/TaskViewIcon.tsx +34 -0
- package/src/icons/WarningAltFilled/WarningAltFilled.tsx +24 -0
- package/src/icons/WarningAltFilledIcon/WarningAltFilledIcon.tsx +24 -0
- package/src/icons/WorkflowAutomationIcon/WorkflowAutomationIcon.tsx +34 -0
- package/src/index.ts +11 -0
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +2 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Fragment, useRef, useState } from 'react';
|
|
1
|
+
import React, { Fragment, useRef, useState, useEffect } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { MouseEvent } from 'react';
|
|
@@ -6,7 +6,6 @@ import type { SearchFacetCount, SearchItemData } from '@redocly/theme/core/types
|
|
|
6
6
|
|
|
7
7
|
import { SearchInput } from '@redocly/theme/components/Search/SearchInput';
|
|
8
8
|
import { SearchShortcut } from '@redocly/theme/components/Search/SearchShortcut';
|
|
9
|
-
import { SearchAiResponse } from '@redocly/theme/components/Search/SearchAiResponse';
|
|
10
9
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
11
10
|
import { breakpoints, concatClassNames } from '@redocly/theme/core/utils';
|
|
12
11
|
import { SearchItem } from '@redocly/theme/components/Search/SearchItem';
|
|
@@ -17,9 +16,13 @@ import { Tag } from '@redocly/theme/components/Tag/Tag';
|
|
|
17
16
|
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
18
17
|
import { SearchFilter } from '@redocly/theme/components/Search/SearchFilter';
|
|
19
18
|
import { SearchGroups } from '@redocly/theme/components/Search/SearchGroups';
|
|
19
|
+
import { Typography } from '@redocly/theme/components/Typography/Typography';
|
|
20
20
|
import { SpinnerLoader } from '@redocly/theme/components/Loaders/SpinnerLoader';
|
|
21
|
+
import { SearchAiDialog } from '@redocly/theme/components/Search/SearchAiDialog';
|
|
21
22
|
import { SettingsIcon } from '@redocly/theme/icons/SettingsIcon/SettingsIcon';
|
|
22
23
|
import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
24
|
+
import { ChevronLeftIcon } from '@redocly/theme/icons/ChevronLeftIcon/ChevronLeftIcon';
|
|
25
|
+
import { EditIcon } from '@redocly/theme/icons/EditIcon/EditIcon';
|
|
23
26
|
|
|
24
27
|
export type SearchDialogProps = {
|
|
25
28
|
onClose: () => void;
|
|
@@ -63,6 +66,12 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
63
66
|
|
|
64
67
|
useDialogHotKeys(modalRef, onClose);
|
|
65
68
|
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (mode === 'ai-dialog' && aiSearch.isGeneratingResponse) {
|
|
71
|
+
setQuery('');
|
|
72
|
+
}
|
|
73
|
+
}, [mode, aiSearch.isGeneratingResponse, setQuery]);
|
|
74
|
+
|
|
66
75
|
const handleOverlayClick = (event: MouseEvent<HTMLElement>) => {
|
|
67
76
|
const target = event.target as HTMLElement;
|
|
68
77
|
if (typeof target.className !== 'string') return;
|
|
@@ -117,6 +126,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
117
126
|
const showResults = !!((filter && filter.length) || query);
|
|
118
127
|
const showSearchFilterButton = advancedSearch && mode === 'search';
|
|
119
128
|
const showAiSearchButton = askAi && mode === 'search';
|
|
129
|
+
const showAiSearchItem = showAiSearchButton && query;
|
|
120
130
|
const showHeaderButtons = showSearchFilterButton || showAiSearchButton;
|
|
121
131
|
|
|
122
132
|
return (
|
|
@@ -129,57 +139,70 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
129
139
|
<SearchDialogWrapper className="scroll-lock" role="dialog">
|
|
130
140
|
<SearchDialogHeader>
|
|
131
141
|
{product && (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</SearchProductTag>
|
|
137
|
-
</>
|
|
142
|
+
<SearchProductTag color="product">
|
|
143
|
+
{product.name}
|
|
144
|
+
<CloseIcon onClick={() => setProduct(undefined)} color="--icon-color-additional" />
|
|
145
|
+
</SearchProductTag>
|
|
138
146
|
)}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
>
|
|
176
|
-
{translate('search.ai.button', 'Search with AI')}
|
|
177
|
-
</SearchAiButton>
|
|
178
|
-
) : null}
|
|
179
|
-
{showSearchFilterButton && (
|
|
180
|
-
<SearchFilterToggleButton icon={<SettingsIcon />} onClick={onFilterToggle} />
|
|
147
|
+
{mode === 'search' ? (
|
|
148
|
+
<>
|
|
149
|
+
<SearchInput
|
|
150
|
+
value={query}
|
|
151
|
+
onChange={setQuery}
|
|
152
|
+
placeholder={translate('search.label', 'Search docs...')}
|
|
153
|
+
isLoading={isSearchLoading}
|
|
154
|
+
onSubmit={
|
|
155
|
+
showAiSearchButton
|
|
156
|
+
? () => {
|
|
157
|
+
setMode('ai-dialog');
|
|
158
|
+
aiSearch.askQuestion(query);
|
|
159
|
+
}
|
|
160
|
+
: undefined
|
|
161
|
+
}
|
|
162
|
+
data-translation-key="search.label"
|
|
163
|
+
/>
|
|
164
|
+
{showHeaderButtons && (
|
|
165
|
+
<SearchHeaderButtons>
|
|
166
|
+
{showAiSearchButton ? (
|
|
167
|
+
<SearchAiButton
|
|
168
|
+
icon={<AiStarsIcon gradient />}
|
|
169
|
+
onClick={() => {
|
|
170
|
+
setMode('ai-dialog');
|
|
171
|
+
if (query.trim()) {
|
|
172
|
+
aiSearch.askQuestion(query);
|
|
173
|
+
}
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{translate('search.ai.button', 'Search with AI')}
|
|
177
|
+
</SearchAiButton>
|
|
178
|
+
) : null}
|
|
179
|
+
{showSearchFilterButton && (
|
|
180
|
+
<SearchFilterToggleButton icon={<SettingsIcon />} onClick={onFilterToggle} />
|
|
181
|
+
)}
|
|
182
|
+
</SearchHeaderButtons>
|
|
181
183
|
)}
|
|
182
|
-
|
|
184
|
+
</>
|
|
185
|
+
) : (
|
|
186
|
+
<AiDialogHeaderWrapper>
|
|
187
|
+
<Button
|
|
188
|
+
variant="secondary"
|
|
189
|
+
onClick={() => {
|
|
190
|
+
setMode('search');
|
|
191
|
+
aiSearch.clearConversation();
|
|
192
|
+
}}
|
|
193
|
+
icon={<ChevronLeftIcon />}
|
|
194
|
+
>
|
|
195
|
+
{translate('search.ai.backToSearch', 'Back to search')}
|
|
196
|
+
</Button>
|
|
197
|
+
<Button
|
|
198
|
+
variant="secondary"
|
|
199
|
+
disabled={!aiSearch.conversation.length}
|
|
200
|
+
onClick={() => aiSearch.clearConversation()}
|
|
201
|
+
icon={<EditIcon />}
|
|
202
|
+
>
|
|
203
|
+
{translate('search.ai.newConversation', 'New conversation')}
|
|
204
|
+
</Button>
|
|
205
|
+
</AiDialogHeaderWrapper>
|
|
183
206
|
)}
|
|
184
207
|
</SearchDialogHeader>
|
|
185
208
|
|
|
@@ -194,6 +217,31 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
194
217
|
onQuickFilterReset={onQuickFilterReset}
|
|
195
218
|
groupField={groupField}
|
|
196
219
|
/>
|
|
220
|
+
|
|
221
|
+
{showAiSearchItem && (
|
|
222
|
+
<SearchWithAI
|
|
223
|
+
onClick={() => {
|
|
224
|
+
setMode('ai-dialog');
|
|
225
|
+
if (query.trim()) {
|
|
226
|
+
aiSearch.askQuestion(query);
|
|
227
|
+
}
|
|
228
|
+
}}
|
|
229
|
+
tabIndex={0}
|
|
230
|
+
role="option"
|
|
231
|
+
aria-selected="true"
|
|
232
|
+
>
|
|
233
|
+
<AiStarsIcon
|
|
234
|
+
color="var(--search-ai-icon-color)"
|
|
235
|
+
size="36px"
|
|
236
|
+
background="var(--search-ai-icon-bg-color)"
|
|
237
|
+
margin="0 var(--spacing-md) 0 0"
|
|
238
|
+
borderRadius="var(--border-radius-lg)"
|
|
239
|
+
/>
|
|
240
|
+
<Typography fontWeight="var(--font-weight-semibold)">{query}</Typography>
|
|
241
|
+
<Typography>- {translate('search.ai.label', 'Ask AI assistant')}</Typography>
|
|
242
|
+
<ReturnKey>⏎</ReturnKey>
|
|
243
|
+
</SearchWithAI>
|
|
244
|
+
)}
|
|
197
245
|
{showResults ? (
|
|
198
246
|
items && Object.keys(items).some((key) => items[key]?.length) ? (
|
|
199
247
|
Object.keys(items).map((key) =>
|
|
@@ -248,12 +296,15 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
248
296
|
)}
|
|
249
297
|
</>
|
|
250
298
|
) : (
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
isGeneratingResponse={aiSearch.isGeneratingResponse}
|
|
299
|
+
<SearchAiDialog
|
|
300
|
+
initialMessage={query}
|
|
254
301
|
response={aiSearch.response}
|
|
255
|
-
|
|
302
|
+
isGeneratingResponse={aiSearch.isGeneratingResponse}
|
|
256
303
|
error={aiSearch.error}
|
|
304
|
+
resources={aiSearch.resources}
|
|
305
|
+
conversation={aiSearch.conversation}
|
|
306
|
+
setConversation={aiSearch.setConversation}
|
|
307
|
+
onMessageSent={aiSearch.askQuestion}
|
|
257
308
|
/>
|
|
258
309
|
)}
|
|
259
310
|
</SearchDialogBody>
|
|
@@ -363,10 +414,18 @@ const SearchDialogHeader = styled.header`
|
|
|
363
414
|
padding: var(--search-modal-header-padding);
|
|
364
415
|
`;
|
|
365
416
|
|
|
417
|
+
const AiDialogHeaderWrapper = styled.div`
|
|
418
|
+
display: flex;
|
|
419
|
+
justify-content: space-between;
|
|
420
|
+
align-items: center;
|
|
421
|
+
width: 100%;
|
|
422
|
+
`;
|
|
423
|
+
|
|
366
424
|
const SearchDialogBody = styled.div`
|
|
367
425
|
display: flex;
|
|
368
426
|
flex-direction: row;
|
|
369
|
-
flex
|
|
427
|
+
flex: 1;
|
|
428
|
+
min-height: 0;
|
|
370
429
|
overflow: hidden;
|
|
371
430
|
|
|
372
431
|
@media screen and (max-width: ${breakpoints.small}) {
|
|
@@ -476,3 +535,36 @@ const AiDisclaimer = styled.div`
|
|
|
476
535
|
color: var(--search-ai-disclaimer-text-color);
|
|
477
536
|
margin: 0 auto;
|
|
478
537
|
`;
|
|
538
|
+
|
|
539
|
+
const SearchWithAI = styled.div`
|
|
540
|
+
display: flex;
|
|
541
|
+
justify-content: flex-start;
|
|
542
|
+
align-items: center;
|
|
543
|
+
cursor: pointer;
|
|
544
|
+
gap: var(--spacing-unit);
|
|
545
|
+
padding: var(--spacing-md);
|
|
546
|
+
|
|
547
|
+
color: var(--search-item-text-color);
|
|
548
|
+
background-color: var(--search-item-bg-color);
|
|
549
|
+
text-decoration: none;
|
|
550
|
+
white-space: normal;
|
|
551
|
+
outline: none;
|
|
552
|
+
border: none;
|
|
553
|
+
|
|
554
|
+
transition: all 0.3s ease;
|
|
555
|
+
|
|
556
|
+
&:focus,
|
|
557
|
+
&:hover {
|
|
558
|
+
color: var(--search-item-text-color-hover);
|
|
559
|
+
background-color: var(--search-item-bg-color-hover);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
& > :first-child {
|
|
563
|
+
margin-right: var(--spacing-xs);
|
|
564
|
+
}
|
|
565
|
+
`;
|
|
566
|
+
|
|
567
|
+
const ReturnKey = styled(Typography)`
|
|
568
|
+
color: var(--search-item-text-color);
|
|
569
|
+
margin-left: auto;
|
|
570
|
+
`;
|
|
@@ -142,12 +142,12 @@ export const search = css`
|
|
|
142
142
|
--search-trigger-icon-size: 16px;
|
|
143
143
|
--search-trigger-line-height: var(--line-height-base);
|
|
144
144
|
|
|
145
|
-
// @tokens End
|
|
146
|
-
|
|
147
145
|
/**
|
|
148
|
-
* @tokens
|
|
146
|
+
* @tokens AI Search
|
|
149
147
|
*/
|
|
150
148
|
|
|
149
|
+
--search-ai-gradient: linear-gradient(to right, #715efe, #ff5cdc);
|
|
150
|
+
|
|
151
151
|
--search-ai-spinner-icon-color: var(--icon-color-interactive);
|
|
152
152
|
--search-ai-checkmark-icon-color: var(--icon-color-interactive);
|
|
153
153
|
--search-ai-response-padding: var(--spacing-lg);
|
|
@@ -163,9 +163,15 @@ export const search = css`
|
|
|
163
163
|
--search-ai-response-body-gap: var(--spacing-xl);
|
|
164
164
|
--search-ai-response-body-padding: 0 40px;
|
|
165
165
|
|
|
166
|
-
--search-ai-
|
|
167
|
-
--search-ai-
|
|
168
|
-
--search-ai-
|
|
166
|
+
--search-ai-text-color: var(--text-color-secondary);
|
|
167
|
+
--search-ai-text-font-size: var(--font-size-lg);
|
|
168
|
+
--search-ai-text-line-height: var(--line-height-lg);
|
|
169
|
+
|
|
170
|
+
--search-ai-user-bg-color: var(--color-blue-6);
|
|
171
|
+
--search-ai-user-text-color: var(--color-static-white);
|
|
172
|
+
--search-ai-assistant-bg-color: var(--layer-color);
|
|
173
|
+
--search-ai-assistant-text-color: var(--text-color-primary);
|
|
174
|
+
--search-ai-assistant-border: 1px solid var(--border-color-primary);
|
|
169
175
|
|
|
170
176
|
--search-ai-resources-gap: var(--spacing-base);
|
|
171
177
|
--search-ai-resources-title-font-weight: var(--font-weight-medium);
|
|
@@ -175,10 +181,110 @@ export const search = css`
|
|
|
175
181
|
--search-ai-resource-tags-gap: var(--spacing-base);
|
|
176
182
|
--search-ai-resource-tag-text-color: var(--text-color-secondary);
|
|
177
183
|
--search-ai-resource-tag-icon-color: var(--text-color-secondary);
|
|
184
|
+
--search-ai-resource-tag-icon-size: 16px;
|
|
185
|
+
|
|
186
|
+
--search-ai-icon-size: 32px;
|
|
187
|
+
--search-ai-icon-bg-color: var(--search-ai-gradient);
|
|
188
|
+
--search-ai-icon-color: var(--color-static-white);
|
|
189
|
+
|
|
190
|
+
--search-ai-thinking-dots-gap: 4px;
|
|
191
|
+
--search-ai-thinking-dots-padding: 4px 0;
|
|
192
|
+
--search-ai-thinking-dot-size: 6px;
|
|
193
|
+
--search-ai-thinking-dot-color: var(--search-ai-gradient);
|
|
178
194
|
|
|
179
195
|
--search-ai-disclaimer-font-size: var(--font-size-sm);
|
|
180
196
|
--search-ai-disclaimer-line-height: var(--line-height-sm);
|
|
181
197
|
--search-ai-disclaimer-text-color: var(--text-color-secondary);
|
|
182
198
|
|
|
199
|
+
|
|
200
|
+
--search-ai-welcome-margin: var(--spacing-md);
|
|
201
|
+
--search-ai-icon-wrapper-padding: var(--spacing-xs);
|
|
202
|
+
|
|
203
|
+
--search-ai-suggestions-title-text-color: var(--text-color-description);
|
|
204
|
+
--search-ai-suggestions-title-font-size: var(--font-size-base);
|
|
205
|
+
--search-ai-suggestions-title-line-height: var(--line-height-base);
|
|
206
|
+
--search-ai-suggestions-title-font-weight: var(--font-weight-light);
|
|
207
|
+
--search-ai-suggestions-text-color: var(--text-color-description);
|
|
208
|
+
|
|
209
|
+
--search-ai-conversation-input-send-button-bg-color: var(--button-bg-color-primary);
|
|
210
|
+
--search-ai-conversation-input-send-button-bg-color-hover: var(--button-bg-color-primary-hover);
|
|
211
|
+
--search-ai-conversation-input-send-button-bg-color-disabled: var(--button-bg-color-disabled);
|
|
212
|
+
--search-ai-conversation-input-send-button-border-color-disabled: var(--button-border-color-disabled);
|
|
213
|
+
--search-ai-conversation-input-send-button-icon-color: var(--color-static-white);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @tokens AI Search Dialog
|
|
217
|
+
*/
|
|
218
|
+
--search-ai-dialog-bg-color: var(--bg-color);
|
|
219
|
+
--search-ai-dialog-header-border: var(--search-modal-border);
|
|
220
|
+
--search-ai-dialog-header-bg-color: var(--search-modal-header-bg-color);
|
|
221
|
+
--search-ai-dialog-header-padding: var(--search-modal-header-padding);
|
|
222
|
+
|
|
223
|
+
--search-ai-dialog-body-padding: var(--search-ai-response-padding);
|
|
224
|
+
--search-ai-dialog-body-gap: var(--spacing-sm);
|
|
225
|
+
|
|
226
|
+
--search-ai-dialog-input-padding: var(--spacing-sm) var(--search-ai-response-padding);
|
|
227
|
+
--search-ai-dialog-input-border: 1px solid var(--border-color-secondary);
|
|
228
|
+
--search-ai-dialog-input-bg-color: var(--bg-color);
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @tokens AI Search Conversation Input
|
|
232
|
+
*/
|
|
233
|
+
--search-ai-conversation-input-bg-color: var(--bg-color);
|
|
234
|
+
--search-ai-conversation-input-padding: var(--spacing-sm) var(--spacing-md);
|
|
235
|
+
--search-ai-conversation-input-border: 1px solid var(--border-color-secondary);
|
|
236
|
+
--search-ai-conversation-input-border-radius: var(--border-radius-lg);
|
|
237
|
+
--search-ai-conversation-input-font-size: var(--font-size-base);
|
|
238
|
+
--search-ai-conversation-input-placeholder-color: var(--search-input-placeholder-color);
|
|
239
|
+
--search-ai-conversation-input-border-color-focus: var(--color-blue-6);
|
|
240
|
+
--search-ai-conversation-input-border-color-disabled: var(--border-color-secondary);
|
|
241
|
+
|
|
242
|
+
--search-ai-conversation-input-send-button-right: 12px;
|
|
243
|
+
--search-ai-conversation-input-send-button-bg-color: var(--button-bg-color-primary);
|
|
244
|
+
--search-ai-conversation-input-send-button-bg-color-hover: var(--button-bg-color-primary-hover);
|
|
245
|
+
--search-ai-conversation-input-send-button-bg-color-disabled: var(--button-bg-color-disabled);
|
|
246
|
+
--search-ai-conversation-input-send-button-border-disabled: 1px solid var(--button-border-color-disabled);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @tokens AI Search Response
|
|
250
|
+
*/
|
|
251
|
+
--search-ai-response-padding: var(--spacing-lg);
|
|
252
|
+
--search-ai-response-gap: var(--spacing-sm);
|
|
253
|
+
--search-ai-response-header-gap: var(--spacing-md);
|
|
254
|
+
--search-ai-response-body-gap: var(--spacing-xl);
|
|
255
|
+
--search-ai-response-body-padding: 0 40px;
|
|
256
|
+
|
|
257
|
+
--search-ai-text-color: var(--text-color-secondary);
|
|
258
|
+
--search-ai-text-font-size: var(--font-size-lg);
|
|
259
|
+
--search-ai-text-line-height: var(--line-height-lg);
|
|
260
|
+
|
|
261
|
+
--search-ai-thinking-text-margin: var(--md-pre-margin) 0;
|
|
262
|
+
|
|
263
|
+
--search-ai-question-font-size: var(--font-size-xl);
|
|
264
|
+
--search-ai-question-font-weight: var(--font-weight-semibold);
|
|
265
|
+
--search-ai-question-line-height: var(--line-height-xl);
|
|
266
|
+
--search-ai-question-text-color: var(--text-color-primary);
|
|
267
|
+
|
|
268
|
+
--search-ai-resources-gap: var(--spacing-base);
|
|
269
|
+
--search-ai-resources-title-font-weight: var(--font-weight-medium);
|
|
270
|
+
--search-ai-resources-title-font-size: var(--font-size-lg);
|
|
271
|
+
--search-ai-resources-title-line-height: var(--line-height-lg);
|
|
272
|
+
|
|
273
|
+
--search-ai-resource-tags-gap: var(--spacing-base);
|
|
274
|
+
--search-ai-resource-tag-text-color: var(--text-color-secondary);
|
|
275
|
+
--search-ai-resource-tag-icon-color: var(--text-color-secondary);
|
|
276
|
+
|
|
277
|
+
--search-ai-suggestions-gap: var(--spacing-sm);
|
|
278
|
+
--search-ai-suggestions-margin-left: var(--spacing-xs);
|
|
279
|
+
--search-ai-suggestion-item-gap: var(--spacing-xs);
|
|
280
|
+
|
|
281
|
+
--search-ai-suggestions-title-text-color: var(--text-color-description);
|
|
282
|
+
--search-ai-suggestions-title-font-size: var(--font-size-base);
|
|
283
|
+
--search-ai-suggestions-title-line-height: var(--line-height-base);
|
|
284
|
+
--search-ai-suggestions-title-font-weight: var(--font-weight-light);
|
|
285
|
+
|
|
286
|
+
--search-ai-spinner-icon-color: var(--icon-color-interactive);
|
|
287
|
+
--search-ai-checkmark-icon-color: var(--icon-color-interactive);
|
|
288
|
+
|
|
183
289
|
// @tokens End
|
|
184
290
|
`;
|
|
@@ -7,6 +7,10 @@ export enum AiSearchError {
|
|
|
7
7
|
EmptyResponse = 'empty_response',
|
|
8
8
|
ErrorProcessingResponse = 'error_processing_response',
|
|
9
9
|
}
|
|
10
|
+
export const enum AiSearchConversationRole {
|
|
11
|
+
USER = 'user',
|
|
12
|
+
ASSISTANT = 'assistant',
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
const defaultErrorConfig: AiSearchErrorConfig = {
|
|
12
16
|
headerKey: 'search.ai.error.header',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
1
|
+
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
import type {
|
|
@@ -52,11 +52,15 @@ export function useCodeWalkthroughControls(
|
|
|
52
52
|
const navigate = useNavigate();
|
|
53
53
|
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
|
54
54
|
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const filtersRef = useRef(filters);
|
|
56
|
+
const inputsRef = useRef(inputs);
|
|
57
|
+
const togglesRef = useRef(toggles);
|
|
58
|
+
|
|
59
|
+
const getInitialState = () => {
|
|
60
|
+
const state: CodeWalkthroughControlsState = {};
|
|
57
61
|
|
|
58
62
|
for (const [id, toggle] of Object.entries(toggles)) {
|
|
59
|
-
|
|
63
|
+
state[id] = {
|
|
60
64
|
...toggle,
|
|
61
65
|
render: true,
|
|
62
66
|
type: 'toggle',
|
|
@@ -65,7 +69,7 @@ export function useCodeWalkthroughControls(
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
for (const [id, input] of Object.entries(inputs)) {
|
|
68
|
-
|
|
72
|
+
state[id] = {
|
|
69
73
|
...input,
|
|
70
74
|
render: true,
|
|
71
75
|
type: 'input',
|
|
@@ -75,7 +79,7 @@ export function useCodeWalkthroughControls(
|
|
|
75
79
|
|
|
76
80
|
for (const [id, filter] of Object.entries(filters)) {
|
|
77
81
|
const defaultValue = filter?.items?.[0]?.value || '';
|
|
78
|
-
|
|
82
|
+
state[id] = {
|
|
79
83
|
...filter,
|
|
80
84
|
render: true,
|
|
81
85
|
type: 'filter',
|
|
@@ -83,8 +87,43 @@ export function useCodeWalkthroughControls(
|
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
return
|
|
87
|
-
}
|
|
90
|
+
return state;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const [controlsState, setControlsState] = useState(getInitialState);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const sameProps = [
|
|
97
|
+
JSON.stringify(filters) === JSON.stringify(filtersRef.current),
|
|
98
|
+
JSON.stringify(inputs) === JSON.stringify(inputsRef.current),
|
|
99
|
+
JSON.stringify(toggles) === JSON.stringify(togglesRef.current),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (sameProps.every(Boolean)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
filtersRef.current = filters;
|
|
107
|
+
inputsRef.current = inputs;
|
|
108
|
+
togglesRef.current = toggles;
|
|
109
|
+
|
|
110
|
+
const newState = getInitialState();
|
|
111
|
+
|
|
112
|
+
// Preserve existing values where control type hasn't changed
|
|
113
|
+
Object.entries(newState).forEach(([id, control]) => {
|
|
114
|
+
const existingControl = controlsState[id];
|
|
115
|
+
if (existingControl && existingControl.type === control.type) {
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
newState[id] = {
|
|
118
|
+
...control,
|
|
119
|
+
value: existingControl.value,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
setControlsState(newState);
|
|
125
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
|
+
}, [filters, inputs, toggles, enableDeepLink]);
|
|
88
127
|
|
|
89
128
|
const changeControlState = (id: string, value: string | boolean) => {
|
|
90
129
|
setControlsState((prev) => {
|
|
@@ -220,7 +259,8 @@ export function useCodeWalkthroughControls(
|
|
|
220
259
|
getFileText,
|
|
221
260
|
populateInputsWithValue,
|
|
222
261
|
};
|
|
223
|
-
|
|
262
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
263
|
+
}, [controlsState]);
|
|
224
264
|
|
|
225
265
|
/**
|
|
226
266
|
* Update the URL search params with the current state of the filters and inputs
|
|
@@ -242,10 +282,10 @@ export function useCodeWalkthroughControls(
|
|
|
242
282
|
|
|
243
283
|
const newSearch = newSearchParams.toString();
|
|
244
284
|
if (newSearch === location.search.substring(1)) return;
|
|
245
|
-
navigate({ search: newSearch });
|
|
285
|
+
navigate({ search: newSearch }, { replace: true });
|
|
246
286
|
// Ignore searchParams in dependency array to avoid infinite re-renders
|
|
247
287
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
248
|
-
}, [
|
|
288
|
+
}, [controlsState]);
|
|
249
289
|
|
|
250
290
|
return {
|
|
251
291
|
changeControlState,
|
|
@@ -38,14 +38,17 @@ export function useCodeWalkthroughSteps(
|
|
|
38
38
|
enableDeepLink ? searchParams.get(ACTIVE_STEP_QUERY_PARAM) : null,
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
42
|
+
const _steps = useMemo(() => steps, [JSON.stringify(steps)]);
|
|
43
|
+
|
|
41
44
|
const register = useCallback(
|
|
42
45
|
(element: HTMLElement) => {
|
|
43
46
|
// for some reason, the observer is not ready immediately
|
|
44
47
|
setTimeout(() => {
|
|
45
48
|
if (observerRef.current) {
|
|
46
49
|
const stepKey = Number(element.dataset.stepKey);
|
|
47
|
-
if (Number.isInteger(stepKey) && stepKey >= 0) {
|
|
48
|
-
|
|
50
|
+
if (Number.isInteger(stepKey) && stepKey >= 0 && _steps[stepKey]) {
|
|
51
|
+
_steps[stepKey].compRef = element;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
observerRef.current.observe(element);
|
|
@@ -53,22 +56,22 @@ export function useCodeWalkthroughSteps(
|
|
|
53
56
|
}
|
|
54
57
|
}, 10);
|
|
55
58
|
},
|
|
56
|
-
[
|
|
59
|
+
[_steps],
|
|
57
60
|
);
|
|
58
61
|
|
|
59
62
|
const unregister = useCallback(
|
|
60
63
|
(element: HTMLElement) => {
|
|
61
64
|
if (observerRef.current) {
|
|
62
65
|
const stepKey = Number(element.dataset.stepKey);
|
|
63
|
-
if (Number.isInteger(stepKey) && stepKey >= 0) {
|
|
64
|
-
|
|
66
|
+
if (Number.isInteger(stepKey) && stepKey >= 0 && _steps[stepKey]) {
|
|
67
|
+
_steps[stepKey].compRef = undefined;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
observerRef.current.unobserve(element);
|
|
68
71
|
observedElementsRef.current.delete(element);
|
|
69
72
|
}
|
|
70
73
|
},
|
|
71
|
-
[
|
|
74
|
+
[_steps],
|
|
72
75
|
);
|
|
73
76
|
|
|
74
77
|
const observerCallback = useCallback(
|
|
@@ -77,7 +80,7 @@ export function useCodeWalkthroughSteps(
|
|
|
77
80
|
return;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
const renderedSteps =
|
|
83
|
+
const renderedSteps = _steps.filter((step) => Boolean(step.compRef));
|
|
81
84
|
|
|
82
85
|
if (renderedSteps.length < 2) {
|
|
83
86
|
setActiveStep(renderedSteps[0]?.id || null);
|
|
@@ -92,7 +95,7 @@ export function useCodeWalkthroughSteps(
|
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
const { intersectionRatio, boundingClientRect, rootBounds, isIntersecting } = entry;
|
|
95
|
-
const step =
|
|
98
|
+
const step = _steps[stepKey];
|
|
96
99
|
|
|
97
100
|
const stepIndex = renderedSteps.findIndex(
|
|
98
101
|
(renderedStep) => renderedStep.stepKey === step.stepKey,
|
|
@@ -130,7 +133,7 @@ export function useCodeWalkthroughSteps(
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
},
|
|
133
|
-
[
|
|
136
|
+
[_steps, activeStep],
|
|
134
137
|
);
|
|
135
138
|
useEffect(() => {
|
|
136
139
|
const filtersElementHeight = filtersElementRef.current?.clientHeight || 0;
|
|
@@ -167,11 +170,11 @@ export function useCodeWalkthroughSteps(
|
|
|
167
170
|
|
|
168
171
|
const newSearch = newSearchParams.toString();
|
|
169
172
|
if (newSearch === location.search.substring(1)) return;
|
|
170
|
-
navigate({ search: newSearch });
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
navigate({ search: newSearch }, { replace: true });
|
|
175
|
+
|
|
173
176
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
174
|
-
}, [activeStep
|
|
177
|
+
}, [activeStep]);
|
|
175
178
|
|
|
176
179
|
return { register, unregister, lockObserver, filtersElementRef, activeStep, setActiveStep };
|
|
177
180
|
}
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -33,3 +33,4 @@ export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-s
|
|
|
33
33
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-controls';
|
|
34
34
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-panel';
|
|
35
35
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-renderable-files';
|
|
36
|
+
export * from '@redocly/theme/core/hooks/use-element-size';
|