@redocly/theme 0.61.1 → 0.62.0-custom.0

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.
Files changed (188) hide show
  1. package/lib/components/AsyncApiDocs/hooks/AfterAsyncApiChannelDescription.d.ts +1 -0
  2. package/lib/components/AsyncApiDocs/hooks/AfterAsyncApiChannelDescription.js +12 -0
  3. package/lib/components/Badge/Badge.d.ts +2 -1
  4. package/lib/components/Badge/Badge.js +24 -2
  5. package/lib/components/Banner/Banner.js +19 -1
  6. package/lib/components/Banner/variables.js +1 -0
  7. package/lib/components/Breadcrumbs/Breadcrumb.js +1 -1
  8. package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +9 -6
  9. package/lib/components/Breadcrumbs/Breadcrumbs.js +24 -15
  10. package/lib/components/Buttons/AIAssistantButton.js +7 -4
  11. package/lib/components/Catalog/CatalogEntities.js +10 -8
  12. package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +2 -2
  13. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +1 -1
  14. package/lib/components/Catalog/CatalogEntity/CatalogEntityProperties/TagsProperty.js +2 -2
  15. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.js +13 -11
  16. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +7 -5
  17. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +9 -7
  18. package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.js +1 -1
  19. package/lib/components/Catalog/CatalogTagsWithTooltip.js +2 -2
  20. package/lib/components/CatalogClassic/CatalogClassicInfoBlock.js +1 -1
  21. package/lib/components/CodeBlock/CodeBlockControls.js +8 -6
  22. package/lib/components/Filter/FilterCheckboxes.js +1 -1
  23. package/lib/components/JsonViewer/JsonViewer.js +2 -2
  24. package/lib/components/JsonViewer/{Helpers.js → helpers.js} +2 -1
  25. package/lib/components/LanguagePicker/LanguagePicker.js +1 -1
  26. package/lib/components/Markdown/Markdown.js +2 -2
  27. package/lib/components/Menu/MenuItem.js +41 -15
  28. package/lib/components/Navbar/NavbarItem.js +1 -1
  29. package/lib/components/OpenApiDocs/hooks/AdditionalOverviewInfo.d.ts +1 -0
  30. package/lib/components/OpenApiDocs/hooks/AdditionalOverviewInfo.js +12 -0
  31. package/lib/components/OpenApiDocs/hooks/AfterOpenApiDescription.d.ts +1 -0
  32. package/lib/components/OpenApiDocs/hooks/AfterOpenApiDescription.js +6 -0
  33. package/lib/components/PageActions/PageActions.js +25 -8
  34. package/lib/components/Search/SearchAiDialog.d.ts +4 -2
  35. package/lib/components/Search/SearchAiDialog.js +23 -4
  36. package/lib/components/Search/SearchAiMessage.d.ts +4 -2
  37. package/lib/components/Search/SearchAiMessage.js +82 -23
  38. package/lib/components/Search/SearchDialog.js +50 -25
  39. package/lib/components/Select/variables.js +2 -2
  40. package/lib/components/SvgViewer/SvgViewer.d.ts +15 -0
  41. package/lib/components/SvgViewer/SvgViewer.js +312 -0
  42. package/lib/components/SvgViewer/variables.d.ts +1 -0
  43. package/lib/components/SvgViewer/variables.dark.d.ts +1 -0
  44. package/lib/components/SvgViewer/variables.dark.js +8 -0
  45. package/lib/components/SvgViewer/variables.js +17 -0
  46. package/lib/components/Tag/Tag.js +1 -1
  47. package/lib/components/Tag/variables.dark.js +6 -0
  48. package/lib/components/Tag/variables.js +6 -0
  49. package/lib/components/Tooltip/Tooltip.d.ts +2 -3
  50. package/lib/components/Tooltip/Tooltip.js +66 -113
  51. package/lib/components/Tooltip/variables.dark.js +4 -0
  52. package/lib/components/Tooltip/variables.js +3 -3
  53. package/lib/components/UserMenu/LoginButton.d.ts +8 -2
  54. package/lib/components/UserMenu/LoginButton.js +4 -3
  55. package/lib/core/constants/search.d.ts +5 -1
  56. package/lib/core/constants/search.js +24 -1
  57. package/lib/core/hooks/search/use-search-dialog.js +2 -2
  58. package/lib/core/hooks/use-color-switcher.js +3 -1
  59. package/lib/core/hooks/use-mcp-config.js +2 -1
  60. package/lib/core/hooks/use-modal-scroll-lock.js +24 -10
  61. package/lib/core/hooks/use-outside-click.d.ts +3 -1
  62. package/lib/core/hooks/use-outside-click.js +8 -4
  63. package/lib/core/hooks/use-page-actions.d.ts +1 -1
  64. package/lib/core/hooks/use-page-actions.js +44 -11
  65. package/lib/core/hooks/use-product-picker.js +1 -1
  66. package/lib/core/hooks/use-unique-svg-ids.d.ts +6 -0
  67. package/lib/core/hooks/use-unique-svg-ids.js +15 -0
  68. package/lib/core/openapi/index.d.ts +1 -0
  69. package/lib/core/openapi/index.js +3 -1
  70. package/lib/core/styles/dark.js +2 -0
  71. package/lib/core/styles/global.js +31 -15
  72. package/lib/core/types/catalog.d.ts +1 -1
  73. package/lib/core/types/hooks.d.ts +23 -2
  74. package/lib/core/types/l10n.d.ts +1 -1
  75. package/lib/core/types/search.d.ts +24 -0
  76. package/lib/core/types/search.js +9 -1
  77. package/lib/core/utils/content-segments.d.ts +2 -0
  78. package/lib/core/utils/content-segments.js +22 -0
  79. package/lib/core/utils/index.d.ts +1 -0
  80. package/lib/core/utils/index.js +1 -0
  81. package/lib/core/utils/transform-revisions-to-version-history.js +8 -51
  82. package/lib/ext/process-scorecard.d.ts +5 -0
  83. package/lib/ext/process-scorecard.js +11 -0
  84. package/lib/icons/FitToViewIcon/FitToViewIcon.d.ts +9 -0
  85. package/lib/icons/FitToViewIcon/FitToViewIcon.js +25 -0
  86. package/lib/index.d.ts +8 -0
  87. package/lib/index.js +8 -0
  88. package/lib/layouts/DocumentationLayout.js +4 -25
  89. package/lib/layouts/DocumentationLayoutBottom.d.ts +11 -0
  90. package/lib/layouts/DocumentationLayoutBottom.js +28 -0
  91. package/lib/layouts/DocumentationLayoutTop.d.ts +13 -0
  92. package/lib/layouts/DocumentationLayoutTop.js +33 -0
  93. package/lib/layouts/Forbidden.js +22 -18
  94. package/lib/markdoc/components/Cards/Card.js +1 -0
  95. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
  96. package/lib/markdoc/components/Heading/Heading.js +40 -2
  97. package/lib/markdoc/components/LoginButton/LoginButton.d.ts +9 -0
  98. package/lib/markdoc/components/LoginButton/LoginButton.js +48 -0
  99. package/lib/markdoc/components/Mermaid/Mermaid.js +70 -2
  100. package/lib/markdoc/components/default.d.ts +1 -0
  101. package/lib/markdoc/components/default.js +1 -0
  102. package/lib/markdoc/default.d.ts +6 -0
  103. package/lib/markdoc/default.js +2 -0
  104. package/lib/markdoc/tags/login-button.d.ts +2 -0
  105. package/lib/markdoc/tags/login-button.js +32 -0
  106. package/package.json +8 -8
  107. package/src/components/AsyncApiDocs/hooks/AfterAsyncApiChannelDescription.tsx +10 -0
  108. package/src/components/Badge/Badge.tsx +18 -2
  109. package/src/components/Banner/Banner.tsx +23 -1
  110. package/src/components/Banner/variables.ts +1 -0
  111. package/src/components/Breadcrumbs/Breadcrumb.tsx +3 -3
  112. package/src/components/Breadcrumbs/BreadcrumbDropdown.tsx +11 -8
  113. package/src/components/Breadcrumbs/Breadcrumbs.tsx +24 -15
  114. package/src/components/Buttons/AIAssistantButton.tsx +7 -4
  115. package/src/components/Catalog/CatalogEntities.tsx +10 -8
  116. package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +1 -1
  117. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +1 -2
  118. package/src/components/Catalog/CatalogEntity/CatalogEntityProperties/TagsProperty.tsx +1 -1
  119. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.tsx +13 -11
  120. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +7 -5
  121. package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +9 -7
  122. package/src/components/Catalog/CatalogTableView/CatalogTableViewRow.tsx +1 -2
  123. package/src/components/Catalog/CatalogTagsWithTooltip.tsx +9 -5
  124. package/src/components/CatalogClassic/CatalogClassicInfoBlock.tsx +3 -1
  125. package/src/components/CodeBlock/CodeBlockControls.tsx +16 -10
  126. package/src/components/Filter/FilterCheckboxes.tsx +1 -1
  127. package/src/components/JsonViewer/JsonViewer.tsx +1 -2
  128. package/src/components/JsonViewer/{Helpers.tsx → helpers.tsx} +1 -0
  129. package/src/components/LanguagePicker/LanguagePicker.tsx +1 -1
  130. package/src/components/Markdown/Markdown.tsx +2 -2
  131. package/src/components/Menu/MenuItem.tsx +61 -16
  132. package/src/components/Navbar/NavbarItem.tsx +3 -1
  133. package/src/components/OpenApiDocs/hooks/AdditionalOverviewInfo.tsx +10 -0
  134. package/src/components/OpenApiDocs/hooks/AfterOpenApiDescription.tsx +2 -0
  135. package/src/components/PageActions/PageActions.tsx +38 -15
  136. package/src/components/Search/SearchAiDialog.tsx +31 -2
  137. package/src/components/Search/SearchAiMessage.tsx +103 -17
  138. package/src/components/Search/SearchDialog.tsx +70 -37
  139. package/src/components/Select/variables.ts +2 -2
  140. package/src/components/SvgViewer/SvgViewer.tsx +405 -0
  141. package/src/components/SvgViewer/variables.dark.ts +5 -0
  142. package/src/components/SvgViewer/variables.ts +14 -0
  143. package/src/components/Tag/Tag.tsx +2 -1
  144. package/src/components/Tag/variables.dark.ts +6 -0
  145. package/src/components/Tag/variables.ts +6 -0
  146. package/src/components/Tooltip/Tooltip.tsx +77 -120
  147. package/src/components/Tooltip/variables.dark.ts +4 -0
  148. package/src/components/Tooltip/variables.ts +3 -3
  149. package/src/components/UserMenu/LoginButton.tsx +23 -8
  150. package/src/core/constants/search.ts +27 -1
  151. package/src/core/hooks/__mocks__/use-theme-hooks.ts +10 -1
  152. package/src/core/hooks/search/use-search-dialog.ts +2 -2
  153. package/src/core/hooks/use-color-switcher.ts +3 -1
  154. package/src/core/hooks/use-mcp-config.ts +2 -1
  155. package/src/core/hooks/use-modal-scroll-lock.ts +29 -10
  156. package/src/core/hooks/use-outside-click.ts +16 -5
  157. package/src/core/hooks/use-page-actions.ts +66 -25
  158. package/src/core/hooks/use-product-picker.ts +1 -1
  159. package/src/core/hooks/use-unique-svg-ids.ts +12 -0
  160. package/src/core/openapi/index.ts +1 -0
  161. package/src/core/styles/dark.ts +2 -0
  162. package/src/core/styles/global.ts +31 -15
  163. package/src/core/types/catalog.ts +1 -1
  164. package/src/core/types/hooks.ts +29 -1
  165. package/src/core/types/l10n.ts +12 -1
  166. package/src/core/types/search.ts +19 -0
  167. package/src/core/utils/content-segments.ts +27 -0
  168. package/src/core/utils/index.ts +1 -0
  169. package/src/core/utils/transform-revisions-to-version-history.ts +8 -80
  170. package/src/ext/process-scorecard.ts +14 -0
  171. package/src/icons/FitToViewIcon/FitToViewIcon.tsx +26 -0
  172. package/src/index.ts +8 -0
  173. package/src/layouts/DocumentationLayout.tsx +4 -30
  174. package/src/layouts/DocumentationLayoutBottom.tsx +42 -0
  175. package/src/layouts/DocumentationLayoutTop.tsx +52 -0
  176. package/src/layouts/Forbidden.tsx +36 -21
  177. package/src/markdoc/components/Cards/Card.tsx +1 -0
  178. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
  179. package/src/markdoc/components/Heading/Heading.tsx +52 -4
  180. package/src/markdoc/components/LoginButton/LoginButton.tsx +38 -0
  181. package/src/markdoc/components/Mermaid/Mermaid.tsx +57 -8
  182. package/src/markdoc/components/default.ts +1 -0
  183. package/src/markdoc/default.ts +2 -0
  184. package/src/markdoc/tags/login-button.ts +30 -0
  185. package/lib/components/Tooltip/TooltipWrapper.d.ts +0 -12
  186. package/lib/components/Tooltip/TooltipWrapper.js +0 -34
  187. package/src/components/Tooltip/TooltipWrapper.tsx +0 -70
  188. /package/lib/components/JsonViewer/{Helpers.d.ts → helpers.d.ts} +0 -0
@@ -5,8 +5,7 @@ import type { JSX } from 'react';
5
5
  import type { CodeBlockControlsProps } from '@redocly/theme/components/CodeBlock/CodeBlockControls';
6
6
 
7
7
  import { CodeBlock } from '@redocly/theme/components/CodeBlock/CodeBlock';
8
-
9
- import { JsonValue } from './Helpers';
8
+ import { JsonValue } from '@redocly/theme/components/JsonViewer/helpers';
10
9
 
11
10
  export type PanelType = 'request' | 'responses' | 'request-samples' | 'response-samples';
12
11
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable check-file/filename-naming-convention */
1
2
  import React, { useEffect } from 'react';
2
3
  import styled from 'styled-components';
3
4
 
@@ -40,7 +40,7 @@ export function LanguagePicker(props: LanguagePickerProps): JSX.Element | null {
40
40
  onAction: () => {
41
41
  setLocale(locale.code);
42
42
  props.onChangeLanguage(locale.code);
43
- telemetry.sendLanguagePickerLocaleChangedMessage({ locale: locale.code });
43
+ telemetry.sendLanguagePickerLocaleChangedMessage([{ object: 'locale', locale: locale.code }]);
44
44
  },
45
45
  active: locale.code === currentLocale.code,
46
46
  suffix: locale.code === currentLocale.code && <CheckmarkIcon />,
@@ -58,7 +58,7 @@ export const Markdown = styled.main.attrs<{
58
58
  ${markdownLinksCss}
59
59
 
60
60
  a[name], [id] {
61
- scroll-margin-top: var(--navbar-height);
61
+ scroll-margin-top: calc(var(--navbar-height) + var(--banner-height));
62
62
  }
63
63
 
64
64
  img,
@@ -76,7 +76,7 @@ export const Markdown = styled.main.attrs<{
76
76
  font-family: var(--heading-font-family);
77
77
  position: relative;
78
78
 
79
- scroll-margin-top: var(--navbar-height);
79
+ scroll-margin-top: calc(var(--navbar-height) + var(--banner-height));
80
80
  }
81
81
 
82
82
  strong {
@@ -13,8 +13,8 @@ import { HttpTag } from '@redocly/theme/components/Tags/HttpTag';
13
13
  import { MenuItemType } from '@redocly/theme/core/constants';
14
14
  import { getMenuItemType, getOperationColor } from '@redocly/theme/core/utils';
15
15
  import { ArrowRightIcon } from '@redocly/theme/icons/ArrowRightIcon/ArrowRightIcon';
16
- import { Badge } from '@redocly/theme/components/Badge/Badge';
17
16
  import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
17
+ import { Tag, ContentWrapper } from '@redocly/theme/components/Tag/Tag';
18
18
 
19
19
  export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Element {
20
20
  const { item, depth, className, onClick } = props;
@@ -37,10 +37,13 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
37
37
  const hasHttpTag = !!item.httpVerb || type === MenuItemType.Operation;
38
38
 
39
39
  const handleOnClick = () => {
40
- telemetry.sendSidebarItemClickedMessage({
41
- label: item.label,
42
- type: item.type === 'link' || item.type === 'group' ? item.type : undefined,
43
- });
40
+ telemetry.sendSidebarItemClickedMessage([
41
+ {
42
+ object: 'sidebar_item',
43
+ label: item.label,
44
+ type: item.type === 'link' || item.type === 'group' ? item.type : undefined,
45
+ },
46
+ ]);
44
47
  onClick?.();
45
48
  if (isNested) {
46
49
  handleExpand();
@@ -92,12 +95,31 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
92
95
  <MenuItemIcon icon={item.icon} srcSet={item.srcSet} />
93
96
  <MenuItemLabelTextWrapper>
94
97
  <MenuItemLabel>
98
+ {item.badges
99
+ ?.filter(({ position }) => position === 'before')
100
+ .map(({ name, color, icon }) => (
101
+ <SidebarTag
102
+ color={isDirectColorValue(color) ? undefined : color}
103
+ $bgColor={isDirectColorValue(color) ? color : undefined}
104
+ key={name}
105
+ icon={icon && <BadgeIcon icon={icon} />}
106
+ >
107
+ {name}
108
+ </SidebarTag>
109
+ ))}
95
110
  <span>{translate(item.labelTranslationKey, item.label)}</span>
96
- {item.badges?.map(({ name, color }) => (
97
- <MenuItemBadge color={color} key={name}>
98
- {name}
99
- </MenuItemBadge>
100
- ))}
111
+ {item.badges
112
+ ?.filter(({ position }) => position !== 'before')
113
+ .map(({ name, color, icon }) => (
114
+ <SidebarTag
115
+ color={isDirectColorValue(color) ? undefined : color}
116
+ $bgColor={isDirectColorValue(color) ? color : undefined}
117
+ key={name}
118
+ icon={icon && <BadgeIcon icon={icon} />}
119
+ >
120
+ {name}
121
+ </SidebarTag>
122
+ ))}
101
123
  {item.external ? <LaunchIcon size="var(--menu-item-external-icon-size)" /> : null}
102
124
  </MenuItemLabel>
103
125
  {item.sublabel ? (
@@ -146,6 +168,12 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
146
168
  );
147
169
  }
148
170
 
171
+ /* for backward compatibility */
172
+ function isDirectColorValue(color?: string) {
173
+ if (!color) return false;
174
+ return color.startsWith('#') || color.startsWith('rgb') || color.startsWith('hsl');
175
+ }
176
+
149
177
  function generateClassName({
150
178
  type,
151
179
  item,
@@ -359,14 +387,31 @@ const MenuItemLabel = styled.span`
359
387
  }
360
388
  `;
361
389
 
362
- const MenuItemBadge = styled(Badge)<{ color?: string }>`
390
+ const SidebarTag = styled(Tag)<{ $bgColor?: string; icon?: React.ReactNode }>`
391
+ ${({ $bgColor }) => $bgColor && `background-color: ${$bgColor};`} /* for backward compatibility */
363
392
  margin-left: 0;
364
- background-color: ${({ color }) => color || 'var(--color-info-base)'};
365
393
  font-size: var(--font-size-sm);
366
394
  line-height: var(--line-height-sm);
367
395
  padding: 0 var(--spacing-xxs);
368
- max-width: 80px;
369
- text-overflow: ellipsis;
370
- white-space: nowrap;
371
- overflow: hidden;
396
+ max-width: 90px;
397
+
398
+ --tag-padding: 0 var(--spacing-xxs);
399
+ --tag-content-padding: 0;
400
+ --tag-font-size: var(--font-size-sm);
401
+ --tag-line-height: var(--line-height-sm);
402
+ vertical-align: middle;
403
+
404
+ ${ContentWrapper} {
405
+ text-overflow: ellipsis;
406
+ white-space: nowrap;
407
+ overflow: hidden;
408
+ display: block;
409
+ }
410
+ `;
411
+
412
+ const BadgeIcon = styled(GenericIcon)`
413
+ --icon-width: var(--font-size-sm);
414
+ --icon-height: var(--font-size-sm);
415
+ margin-right: var(--spacing-xxs);
416
+ flex-shrink: 0;
372
417
  `;
@@ -47,7 +47,9 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
47
47
  as={item.link ? Link : undefined}
48
48
  active={isActive}
49
49
  className={className}
50
- onClick={() => telemetry.sendNavbarMenuItemClickedMessage({ type: item.type })}
50
+ onClick={() =>
51
+ telemetry.sendNavbarMenuItemClickedMessage([{ object: 'menu_item', type: item.type }])
52
+ }
51
53
  external={item.external}
52
54
  target={item.target}
53
55
  to={item.link}
@@ -0,0 +1,10 @@
1
+ // interface AdditionalOverviewInfoProps {
2
+ // info: any;
3
+ // }
4
+
5
+ // export function AdditionalOverviewInfo(_props: AdditionalOverviewInfoProps) {
6
+ // return null;
7
+ // }
8
+
9
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
10
+ export const AdditionalOverviewInfo: any = null;
@@ -0,0 +1,2 @@
1
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
2
+ export const AfterOpenApiDescription: any = null;
@@ -3,9 +3,9 @@ import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
  import type { PageAction } from '@redocly/theme/core/types';
6
- import type { DropdownMenuItemProps } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
7
6
 
8
7
  import { PageActionsMenuItem } from '@redocly/theme/components/PageActions/PageActionsMenuItem';
8
+ import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
9
9
  import { Link } from '@redocly/theme/components/Link/Link';
10
10
  import { ButtonGroup } from '@redocly/theme/components/Button/ButtonGroup';
11
11
  import { Button } from '@redocly/theme/components/Button/Button';
@@ -52,20 +52,27 @@ export function PageActions(props: PageActionProps): JSX.Element | null {
52
52
  }, ACTION_DONE_DISPLAY_DURATION);
53
53
  };
54
54
 
55
- const menuItems: DropdownMenuItemProps[] = actions.map((action) => ({
56
- content:
57
- 'link' in action ? (
58
- <LinkMenuItem to={action.link} external>
59
- <PageActionsMenuItem pageAction={action} />
60
- </LinkMenuItem>
61
- ) : (
55
+ const menuItems = actions.map((action, index) => {
56
+ const key = `${action.title}-${index}`;
57
+ const hasLink = 'link' in action;
58
+
59
+ const content = hasLink ? (
60
+ <LinkMenuItem key={`${key}-link`} to={action.link} tabIndex={-1} external>
62
61
  <PageActionsMenuItem pageAction={action} />
63
- ),
64
- onAction: 'onClick' in action ? () => handleActionClick(action) : undefined,
65
- }));
62
+ </LinkMenuItem>
63
+ ) : (
64
+ <PageActionsMenuItem pageAction={action} />
65
+ );
66
+
67
+ return (
68
+ <StyledDropdownMenuItem key={key} onAction={() => handleActionClick(action)}>
69
+ {content}
70
+ </StyledDropdownMenuItem>
71
+ );
72
+ });
66
73
 
67
74
  return (
68
- <PageActionsWrapper>
75
+ <PageActionsWrapper data-component-name="PageActions/PageActions">
69
76
  <ButtonGroup variant="outlined" size="medium">
70
77
  <Button
71
78
  icon={renderIcon(buttonAction, actionState)}
@@ -76,9 +83,9 @@ export function PageActions(props: PageActionProps): JSX.Element | null {
76
83
  {buttonAction.buttonText}
77
84
  </Button>
78
85
  {actions.length > 1 ? (
79
- <Dropdown withArrow trigger={<Button />} placement="bottom" alignment="end">
80
- <StyledDropdownMenu items={menuItems} />
81
- </Dropdown>
86
+ <StyledDropdown withArrow trigger={<Button />} placement="bottom" alignment="end">
87
+ <StyledDropdownMenu>{menuItems}</StyledDropdownMenu>
88
+ </StyledDropdown>
82
89
  ) : null}
83
90
  </ButtonGroup>
84
91
  </PageActionsWrapper>
@@ -111,6 +118,22 @@ const LinkMenuItem = styled(Link)`
111
118
  --link-decoration-hover: none;
112
119
  `;
113
120
 
121
+ const StyledDropdown = styled(Dropdown)`
122
+ z-index: calc(var(--z-index-raised) - 1);
123
+ `;
124
+
114
125
  const StyledDropdownMenu = styled(DropdownMenu)`
115
126
  --dropdown-menu-max-height: var(--page-actions-dropdown-max-height);
116
127
  `;
128
+
129
+ const StyledDropdownMenuItem = styled(DropdownMenuItem)`
130
+ &:has(a) {
131
+ padding: 0;
132
+ & > a {
133
+ display: inline-block;
134
+ width: 100%;
135
+ padding: var(--dropdown-menu-item-padding-vertical)
136
+ var(--dropdown-menu-item-padding-horizontal);
137
+ }
138
+ }
139
+ `;
@@ -6,6 +6,8 @@ import type {
6
6
  AiSearchConversationItem,
7
7
  SearchAiMessageResource,
8
8
  FeedbackType,
9
+ ToolCall,
10
+ ContentSegment,
9
11
  } from '@redocly/theme/core/types';
10
12
 
11
13
  import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
@@ -34,6 +36,8 @@ export type SearchAiDialogProps = {
34
36
  history?: AiSearchConversationItem[],
35
37
  messageId?: string,
36
38
  ) => void;
39
+ toolCalls?: ToolCall[];
40
+ contentSegments?: ContentSegment[];
37
41
  };
38
42
 
39
43
  export function SearchAiDialog({
@@ -46,6 +50,8 @@ export function SearchAiDialog({
46
50
  className,
47
51
  conversation,
48
52
  setConversation,
53
+ toolCalls = [],
54
+ contentSegments,
49
55
  }: SearchAiDialogProps): JSX.Element {
50
56
  const { useTranslate } = useThemeHooks();
51
57
  const { aiAssistant } = useThemeConfig();
@@ -111,13 +117,32 @@ export function SearchAiDialog({
111
117
  content,
112
118
  resources,
113
119
  messageId: lastMessage.messageId,
120
+ toolCalls,
121
+ contentSegments,
114
122
  },
115
123
  ];
116
124
  }
117
125
 
118
- return [...prev, { role: AiSearchConversationRole.ASSISTANT, content, resources }];
126
+ return [
127
+ ...prev,
128
+ {
129
+ role: AiSearchConversationRole.ASSISTANT,
130
+ content,
131
+ resources,
132
+ toolCalls,
133
+ contentSegments,
134
+ },
135
+ ];
119
136
  });
120
- }, [response, conversation.length, error, resources, setConversation]);
137
+ }, [
138
+ response,
139
+ conversation.length,
140
+ error,
141
+ resources,
142
+ toolCalls,
143
+ contentSegments,
144
+ setConversation,
145
+ ]);
121
146
 
122
147
  useEffect(() => {
123
148
  if (error) {
@@ -171,6 +196,10 @@ export function SearchAiDialog({
171
196
  messageId={item.messageId}
172
197
  feedback={item.feedback}
173
198
  onFeedbackChange={handleFeedbackChange}
199
+ toolCalls={
200
+ item.toolCalls || (index === conversation.length - 1 ? toolCalls : undefined)
201
+ }
202
+ contentSegments={item.contentSegments}
174
203
  />
175
204
  ))}
176
205
 
@@ -3,7 +3,13 @@ import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
 
6
- import { FeedbackType, type SearchAiMessageResource } from '@redocly/theme/core/types';
6
+ import {
7
+ FeedbackType,
8
+ type SearchAiMessageResource,
9
+ type ToolCall,
10
+ type ContentSegment,
11
+ } from '@redocly/theme/core/types';
12
+ import { TOOL_CALL_DISPLAY_TEXT } from '@redocly/theme/core/constants';
7
13
  import { Link } from '@redocly/theme/components/Link/Link';
8
14
  import { Tag } from '@redocly/theme/components/Tag/Tag';
9
15
  import { AiSearchConversationRole } from '@redocly/theme/core/constants';
@@ -12,9 +18,14 @@ import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
12
18
  import { DocumentIcon } from '@redocly/theme/icons/DocumentIcon/DocumentIcon';
13
19
  import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
14
20
  import { CheckmarkOutlineIcon } from '@redocly/theme/icons/CheckmarkOutlineIcon/CheckmarkOutlineIcon';
21
+ import { SearchAiActionButtons } from '@redocly/theme/components/Search/SearchAiActionButtons';
22
+ import { SearchAiNegativeFeedbackForm } from '@redocly/theme/components/Search/SearchAiNegativeFeedbackForm';
15
23
 
16
- import { SearchAiActionButtons } from './SearchAiActionButtons';
17
- import { SearchAiNegativeFeedbackForm } from './SearchAiNegativeFeedbackForm';
24
+ function MarkdownSegment({ text }: { text: string }): JSX.Element {
25
+ const { useMarkdownText } = useThemeHooks();
26
+ const markdown = useMarkdownText(text);
27
+ return <ResponseText as="div" children={markdown} data-testid="response-text" />;
28
+ }
18
29
 
19
30
  export type SearchAiMessageProps = {
20
31
  role: AiSearchConversationRole;
@@ -25,6 +36,8 @@ export type SearchAiMessageProps = {
25
36
  messageId?: string;
26
37
  feedback?: FeedbackType;
27
38
  onFeedbackChange: (messageId: string, feedback: FeedbackType | undefined) => void;
39
+ toolCalls?: ToolCall[];
40
+ contentSegments?: ContentSegment[];
28
41
  };
29
42
 
30
43
  function SearchAiMessageComponent({
@@ -36,9 +49,10 @@ function SearchAiMessageComponent({
36
49
  messageId,
37
50
  feedback,
38
51
  onFeedbackChange,
52
+ toolCalls = [],
53
+ contentSegments = [{ type: 'text' as const, text: content }],
39
54
  }: SearchAiMessageProps): JSX.Element {
40
- const { useMarkdownText, useTranslate, useTelemetry } = useThemeHooks();
41
- const markDownContent = useMarkdownText(content || '');
55
+ const { useTranslate, useTelemetry } = useThemeHooks();
42
56
  const { translate } = useTranslate();
43
57
  const telemetry = useTelemetry();
44
58
  const [feedbackSent, setFeedbackSent] = useState(false);
@@ -48,15 +62,20 @@ function SearchAiMessageComponent({
48
62
 
49
63
  const showSuccessMessage = feedbackSent && feedback;
50
64
 
65
+ const isLoading = isThinking && content.length === 0 && toolCalls.length === 0;
66
+
51
67
  const sendFeedbackTelemetry = (feedbackValue: FeedbackType, dislikeReason?: string) => {
52
68
  if (!messageId) return;
53
69
 
54
70
  try {
55
- telemetry.sendSearchAIFeedbackMessage({
56
- feedback: feedbackValue,
57
- messageId,
58
- reason: dislikeReason,
59
- });
71
+ telemetry.sendSearchAIFeedbackMessage([
72
+ {
73
+ object: 'feedback',
74
+ feedback: feedbackValue,
75
+ messageId,
76
+ reason: dislikeReason,
77
+ },
78
+ ]);
60
79
  } catch (error) {
61
80
  console.error('Error sending feedback', error);
62
81
  }
@@ -98,7 +117,36 @@ function SearchAiMessageComponent({
98
117
  {role === AiSearchConversationRole.ASSISTANT ? (
99
118
  <>
100
119
  <MessageWrapper role={role}>
101
- <ResponseText as="div" children={markDownContent} data-testid="response-text" />
120
+ {contentSegments.map((segment, index) => {
121
+ if (segment.type === 'tool') {
122
+ const toolCallCompleted = Boolean(segment.toolCall.result);
123
+
124
+ const toolCallCompletedText =
125
+ TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.completedText ??
126
+ `${translate('search.ai.toolResult.found', 'Found')} ${segment.toolCall.result?.documentCount ?? 0} ${translate('search.ai.toolResult.found.documents', 'documents')}`;
127
+
128
+ const toolCallInProgressText =
129
+ TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.inProgressText ??
130
+ translate('search.ai.toolCall.searching', 'Searching...');
131
+
132
+ const toolCallDisplayText = toolCallCompleted
133
+ ? toolCallCompletedText
134
+ : toolCallInProgressText;
135
+
136
+ return (
137
+ <ToolCallsInfoWrapper key={`tool-${index}`} data-testid="tool-calls-info">
138
+ <ToolCallInfoItem>
139
+ <DocumentIcon size="14px" color="--search-ai-text-color" />
140
+ <ToolCallText $isSearching={!toolCallCompleted}>
141
+ {toolCallDisplayText}
142
+ </ToolCallText>
143
+ </ToolCallInfoItem>
144
+ </ToolCallsInfoWrapper>
145
+ );
146
+ }
147
+
148
+ return <MarkdownSegment key={`text-${index}`} text={segment.text} />;
149
+ })}
102
150
  {hasResources && (
103
151
  <>
104
152
  <ResourcesWrapper data-testid="resources-wrapper">
@@ -107,8 +155,8 @@ function SearchAiMessageComponent({
107
155
  {translate('search.ai.resourcesFound.resources', 'resources')}
108
156
  </ResourcesTitle>
109
157
  <ResourceTagsWrapper>
110
- {resources?.map((resource, idx) => (
111
- <Link key={`${resource.url}-${idx}`} to={resource.url} target="_blank">
158
+ {resources?.map((resource, index) => (
159
+ <Link key={`${resource.url}-${index}`} to={resource.url} target="_blank">
112
160
  <ResourceTag
113
161
  borderless
114
162
  icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
@@ -121,7 +169,7 @@ function SearchAiMessageComponent({
121
169
  </ResourcesWrapper>
122
170
  </>
123
171
  )}
124
- {isThinking && content.length === 0 && (
172
+ {isLoading && (
125
173
  <ThinkingDotsWrapper data-testid="thinking-dots-wrapper">
126
174
  <ThinkingDot />
127
175
  <ThinkingDot />
@@ -188,7 +236,9 @@ export const SearchAiMessage = memo(SearchAiMessageComponent, (prevProps, nextPr
188
236
  prevProps.messageId === nextProps.messageId &&
189
237
  prevProps.feedback === nextProps.feedback &&
190
238
  prevProps.onFeedbackChange === nextProps.onFeedbackChange &&
191
- areResourcesEqual(prevProps.resources, nextProps.resources)
239
+ areResourcesEqual(prevProps.resources, nextProps.resources) &&
240
+ prevProps.toolCalls?.length === nextProps.toolCalls?.length &&
241
+ prevProps.contentSegments === nextProps.contentSegments
192
242
  );
193
243
  });
194
244
 
@@ -228,8 +278,9 @@ const ResponseText = styled(Markdown)`
228
278
 
229
279
  const MessageWrapper = styled.div<{ role: string }>`
230
280
  padding: ${({ role }) =>
231
- role === AiSearchConversationRole.USER ? 'var(--spacing-sm)' : 'var(--spacing-xs)'}
232
- var(--spacing-sm);
281
+ role === AiSearchConversationRole.USER
282
+ ? 'var(--spacing-sm)'
283
+ : 'var(--spacing-sm) var(--spacing-sm) var(--spacing-xs) var(--spacing-sm)'};
233
284
  border-radius: var(--border-radius-lg);
234
285
  width: fit-content;
235
286
  max-width: 100%;
@@ -344,3 +395,38 @@ const SuccessMessageText = styled.div`
344
395
  font-size: var(--font-size-base);
345
396
  color: var(--color-success-darker);
346
397
  `;
398
+
399
+ const ToolCallsInfoWrapper = styled.div`
400
+ display: flex;
401
+ flex-direction: column;
402
+ gap: var(--spacing-xxs);
403
+ margin: 0 0 var(--spacing-sm) 0;
404
+ font-size: var(--font-size-xs);
405
+ color: var(--search-ai-text-color);
406
+ opacity: 0.6;
407
+ `;
408
+
409
+ const ToolCallInfoItem = styled.div`
410
+ display: flex;
411
+ align-items: center;
412
+ gap: var(--spacing-xxs);
413
+ `;
414
+
415
+ const ToolCallText = styled.span<{ $isSearching?: boolean }>`
416
+ font-weight: var(--font-weight-regular);
417
+
418
+ ${({ $isSearching }) =>
419
+ $isSearching &&
420
+ `
421
+ animation: pulse 1.5s ease-in-out infinite;
422
+
423
+ @keyframes pulse {
424
+ 0%, 100% {
425
+ opacity: 1;
426
+ }
427
+ 50% {
428
+ opacity: 0.4;
429
+ }
430
+ }
431
+ `}
432
+ `;