@redocly/theme 0.70.0-next.5 → 0.70.0-next.7

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 (99) hide show
  1. package/lib/components/Buttons/ConnectMCPButton.js +8 -13
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
  3. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
  4. package/lib/components/Dropdown/Dropdown.d.ts +4 -2
  5. package/lib/components/Dropdown/Dropdown.js +28 -8
  6. package/lib/components/Filter/FilterDateRange.js +36 -2
  7. package/lib/components/Portal/Portal.js +37 -5
  8. package/lib/components/Search/SearchAiActionButtons.js +8 -3
  9. package/lib/components/Search/SearchAiActivity.d.ts +10 -0
  10. package/lib/components/Search/SearchAiActivity.js +278 -0
  11. package/lib/components/Search/SearchAiDialog.js +5 -1
  12. package/lib/components/Search/SearchAiMessage.d.ts +2 -1
  13. package/lib/components/Search/SearchAiMessage.js +20 -108
  14. package/lib/components/Search/SearchDialog.js +10 -0
  15. package/lib/components/Search/SearchItem.js +1 -8
  16. package/lib/components/Search/variables.js +15 -2
  17. package/lib/components/Select/SelectInput.js +2 -8
  18. package/lib/components/SplitButton/SplitButton.js +37 -3
  19. package/lib/core/constants/mcp.d.ts +2 -2
  20. package/lib/core/constants/mcp.js +3 -8
  21. package/lib/core/constants/search.js +5 -1
  22. package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
  23. package/lib/core/contexts/PortalContainerContext.js +7 -0
  24. package/lib/core/hooks/index.d.ts +2 -0
  25. package/lib/core/hooks/index.js +2 -0
  26. package/lib/core/hooks/menu/use-collapse.js +1 -1
  27. package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
  28. package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
  29. package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
  30. package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
  31. package/lib/core/hooks/use-connect-mcp-button.js +2 -1
  32. package/lib/core/hooks/use-full-height.d.ts +1 -1
  33. package/lib/core/hooks/use-full-height.js +8 -8
  34. package/lib/core/styles/global.d.ts +2 -0
  35. package/lib/core/styles/global.js +80 -76
  36. package/lib/core/styles/index.d.ts +1 -0
  37. package/lib/core/styles/index.js +3 -1
  38. package/lib/core/styles/search-ai-appear.d.ts +1 -0
  39. package/lib/core/styles/search-ai-appear.js +18 -0
  40. package/lib/core/types/dropdown.d.ts +1 -0
  41. package/lib/core/types/dropdown.js +3 -0
  42. package/lib/core/types/index.d.ts +1 -0
  43. package/lib/core/types/l10n.d.ts +1 -1
  44. package/lib/core/types/mcp.d.ts +2 -1
  45. package/lib/core/types/search.d.ts +23 -0
  46. package/lib/core/utils/assign-ref.d.ts +2 -0
  47. package/lib/core/utils/assign-ref.js +12 -0
  48. package/lib/core/utils/content-segments.d.ts +12 -1
  49. package/lib/core/utils/content-segments.js +90 -5
  50. package/lib/core/utils/dropdown.d.ts +2 -0
  51. package/lib/core/utils/dropdown.js +12 -0
  52. package/lib/core/utils/index.d.ts +2 -0
  53. package/lib/core/utils/index.js +2 -0
  54. package/lib/core/utils/mcp.d.ts +2 -1
  55. package/lib/core/utils/mcp.js +5 -0
  56. package/lib/core/utils/search-ai-activity.d.ts +4 -0
  57. package/lib/core/utils/search-ai-activity.js +83 -0
  58. package/lib/markdoc/tags/connect-mcp.js +15 -0
  59. package/package.json +3 -2
  60. package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
  61. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
  62. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
  63. package/src/components/Dropdown/Dropdown.tsx +28 -6
  64. package/src/components/Filter/FilterDateRange.tsx +3 -2
  65. package/src/components/Portal/Portal.tsx +4 -2
  66. package/src/components/Search/SearchAiActionButtons.tsx +31 -19
  67. package/src/components/Search/SearchAiActivity.tsx +318 -0
  68. package/src/components/Search/SearchAiDialog.tsx +4 -3
  69. package/src/components/Search/SearchAiMessage.tsx +27 -135
  70. package/src/components/Search/SearchDialog.tsx +11 -0
  71. package/src/components/Search/SearchItem.tsx +2 -9
  72. package/src/components/Search/variables.ts +15 -2
  73. package/src/components/Select/SelectInput.tsx +2 -8
  74. package/src/components/SplitButton/SplitButton.tsx +8 -2
  75. package/src/core/constants/mcp.ts +3 -9
  76. package/src/core/constants/search.ts +5 -1
  77. package/src/core/contexts/PortalContainerContext.tsx +4 -0
  78. package/src/core/hooks/__mocks__/index.ts +2 -0
  79. package/src/core/hooks/index.ts +2 -0
  80. package/src/core/hooks/menu/use-collapse.ts +1 -1
  81. package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
  82. package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
  83. package/src/core/hooks/use-connect-mcp-button.ts +7 -2
  84. package/src/core/hooks/use-full-height.ts +8 -8
  85. package/src/core/styles/global.ts +80 -75
  86. package/src/core/styles/index.ts +2 -1
  87. package/src/core/styles/search-ai-appear.ts +16 -0
  88. package/src/core/types/dropdown.ts +1 -0
  89. package/src/core/types/index.ts +1 -0
  90. package/src/core/types/l10n.ts +4 -1
  91. package/src/core/types/mcp.ts +3 -1
  92. package/src/core/types/search.ts +21 -0
  93. package/src/core/utils/assign-ref.ts +9 -0
  94. package/src/core/utils/content-segments.ts +98 -6
  95. package/src/core/utils/dropdown.ts +14 -0
  96. package/src/core/utils/index.ts +2 -0
  97. package/src/core/utils/mcp.ts +7 -1
  98. package/src/core/utils/search-ai-activity.ts +110 -0
  99. package/src/markdoc/tags/connect-mcp.ts +16 -1
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSearchAiActivityStatus = getSearchAiActivityStatus;
4
+ exports.getSearchAiActivityStep = getSearchAiActivityStep;
5
+ const search_1 = require("../types/search");
6
+ const search_2 = require("../constants/search");
7
+ const type_guards_1 = require("./type-guards");
8
+ function getSearchAiActivityStatus(toolCalls, isActive, runningToolCall, translate) {
9
+ var _a;
10
+ const latestToolCall = toolCalls.at(-1);
11
+ if (!latestToolCall) {
12
+ return {
13
+ label: translate('search.ai.thinkingText', 'Thinking...'),
14
+ kind: 'thinking',
15
+ isRunning: true,
16
+ };
17
+ }
18
+ if (isActive || toolCalls.length === 1) {
19
+ const toolCall = runningToolCall !== null && runningToolCall !== void 0 ? runningToolCall : latestToolCall;
20
+ return Object.assign(Object.assign({}, getToolCallLabel(toolCall, !isActive, translate)), { kind: getSearchAiActivityKind(toolCall.name), isRunning: isActive });
21
+ }
22
+ const isSameTool = toolCalls.every((toolCall) => toolCall.name === latestToolCall.name);
23
+ const sharedText = isSameTool
24
+ ? (_a = search_2.TOOL_CALL_DISPLAY_TEXT[latestToolCall.name]) === null || _a === void 0 ? void 0 : _a.completedText
25
+ : undefined;
26
+ return {
27
+ label: sharedText !== null && sharedText !== void 0 ? sharedText : translate('search.ai.toolCall.usedTools', 'Used tools'),
28
+ kind: isSameTool ? getSearchAiActivityKind(latestToolCall.name) : 'code',
29
+ isRunning: false,
30
+ };
31
+ }
32
+ function getSearchAiActivityStep(toolCall, isRunning, translate) {
33
+ const detail = getToolCallDetail(toolCall);
34
+ const kind = getSearchAiActivityKind(toolCall.name);
35
+ if (toolCall.name === search_1.ToolCallName.SearchDocumentation && detail) {
36
+ return { id: toolCall.id, label: `“${detail}”`, kind, isRunning };
37
+ }
38
+ const { label } = getToolCallLabel(toolCall, !isRunning, translate);
39
+ return { id: toolCall.id, label: detail ? label : stripEllipsis(label), kind, isRunning };
40
+ }
41
+ function getToolCallLabel(toolCall, isCompleted, translate) {
42
+ const detail = getToolCallDetail(toolCall);
43
+ if (toolCall.name === search_1.ToolCallName.Execute && detail) {
44
+ return { label: detail };
45
+ }
46
+ const builtInText = search_2.TOOL_CALL_DISPLAY_TEXT[toolCall.name];
47
+ if (builtInText) {
48
+ const text = isCompleted ? builtInText.completedText : builtInText.inProgressText;
49
+ return detail ? { label: stripEllipsis(text), detail } : { label: text };
50
+ }
51
+ const prefix = isCompleted
52
+ ? translate('search.ai.toolCall.executed', 'Executed')
53
+ : translate('search.ai.toolCall.executing', 'Executing');
54
+ return { label: `${prefix} ${toolCall.name}` };
55
+ }
56
+ function getSearchAiActivityKind(name) {
57
+ if (name === search_1.ToolCallName.SearchDocumentation)
58
+ return 'search';
59
+ if (name === search_1.ToolCallName.ContactSupport)
60
+ return 'support';
61
+ return 'code';
62
+ }
63
+ function getToolCallDetail(toolCall) {
64
+ switch (toolCall.name) {
65
+ case search_1.ToolCallName.SearchDocumentation:
66
+ return getStringArg(toolCall.args, 'query');
67
+ case search_1.ToolCallName.Execute:
68
+ return getStringArg(toolCall.args, 'description');
69
+ default:
70
+ return undefined;
71
+ }
72
+ }
73
+ function getStringArg(args, name) {
74
+ if (!(0, type_guards_1.isObject)(args) || !(name in args)) {
75
+ return undefined;
76
+ }
77
+ const value = Reflect.get(args, name);
78
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
79
+ }
80
+ function stripEllipsis(text) {
81
+ return text.replace(/(\.{3}|…)$/, '');
82
+ }
83
+ //# sourceMappingURL=search-ai-activity.js.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.connectMcp = void 0;
4
4
  const constants_1 = require("../../core/constants");
5
+ const utils_1 = require("../../core/utils");
5
6
  exports.connectMcp = {
6
7
  schema: {
7
8
  render: 'ConnectMCP',
@@ -19,6 +20,20 @@ exports.connectMcp = {
19
20
  options: {
20
21
  type: Array,
21
22
  default: constants_1.DEFAULT_MCP_OPTIONS,
23
+ validate(value) {
24
+ if (!Array.isArray(value))
25
+ return [];
26
+ const unknownOptions = value.filter((option) => !(0, utils_1.isMCPOption)(option));
27
+ if (unknownOptions.length === 0)
28
+ return [];
29
+ return [
30
+ {
31
+ id: 'invalid-connect-mcp-options',
32
+ level: 'error',
33
+ message: `Invalid connect-mcp options: ${unknownOptions.join(', ')}. Must be one of: ${constants_1.MCP_OPTIONS.join(', ')}`,
34
+ },
35
+ ];
36
+ },
22
37
  },
23
38
  },
24
39
  selfClosing: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.70.0-next.5",
3
+ "version": "0.70.0-next.7",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -43,6 +43,7 @@
43
43
  "@babel/core": "7.29.7",
44
44
  "@markdoc/markdoc": "0.5.2",
45
45
  "@redocly/realm-asyncapi-sdk": "0.16.0-next.2",
46
+ "@redocly/tooling-test-utils": "0.0.0",
46
47
  "@shikijs/types": "3.21.0",
47
48
  "@testing-library/react": "16.3.0",
48
49
  "@testing-library/user-event": "14.6.1",
@@ -71,7 +72,7 @@
71
72
  "webpack": "5.105.2"
72
73
  },
73
74
  "dependencies": {
74
- "@redocly/config": "0.61.1",
75
+ "@redocly/config": "0.62.0",
75
76
  "@tanstack/react-query": "5.62.3",
76
77
  "@tanstack/react-virtual": "3.13.0",
77
78
  "@xyflow/react": "12.8.3",
@@ -19,7 +19,6 @@ import { useConnectMCPButton, useThemeHooks } from '@redocly/theme/core/hooks';
19
19
  import { DEFAULT_MCP_OPTIONS } from '@redocly/theme/core/constants';
20
20
 
21
21
  type MenuOption = {
22
- key: MCPOption;
23
22
  icon: React.ComponentType;
24
23
  titleTranslationKey: string;
25
24
  titleDefault: string;
@@ -27,48 +26,43 @@ type MenuOption = {
27
26
  descriptionDefault: string;
28
27
  };
29
28
 
30
- const MENU_OPTIONS: MenuOption[] = [
31
- {
32
- key: 'cursor',
29
+ const MENU_OPTIONS: Record<MCPOption, MenuOption> = {
30
+ cursor: {
33
31
  icon: CursorIcon,
34
32
  titleTranslationKey: 'page.actions.connectMcp.cursor',
35
33
  titleDefault: 'Connect to Cursor',
36
34
  descriptionTranslationKey: 'page.actions.connectMcp.cursorDescription',
37
35
  descriptionDefault: 'Install MCP server on Cursor',
38
36
  },
39
- {
40
- key: 'vscode',
37
+ vscode: {
41
38
  icon: VSCodeIcon,
42
39
  titleTranslationKey: 'page.actions.connectMcp.vscode',
43
40
  titleDefault: 'Connect to VS Code',
44
41
  descriptionTranslationKey: 'page.actions.connectMcp.vscodeDescription',
45
42
  descriptionDefault: 'Install MCP server on VS Code',
46
43
  },
47
- {
48
- key: 'claude-code',
44
+ 'claude-code': {
49
45
  icon: ClaudeIcon,
50
46
  titleTranslationKey: 'page.actions.connectMcp.claudeCode',
51
47
  titleDefault: 'Connect to Claude Code',
52
48
  descriptionTranslationKey: 'page.actions.connectMcp.claudeCodeDescription',
53
49
  descriptionDefault: 'Copy the Claude Code CLI command',
54
50
  },
55
- {
56
- key: 'codex',
51
+ codex: {
57
52
  icon: CodexIcon,
58
53
  titleTranslationKey: 'page.actions.connectMcp.codex',
59
54
  titleDefault: 'Connect to Codex CLI',
60
55
  descriptionTranslationKey: 'page.actions.connectMcp.codexDescription',
61
56
  descriptionDefault: 'Copy the Codex CLI command',
62
57
  },
63
- {
64
- key: 'copy',
58
+ copy: {
65
59
  icon: CopyIcon,
66
60
  titleTranslationKey: 'page.actions.connectMcp.copyConfig',
67
61
  titleDefault: 'Copy MCP Configuration',
68
62
  descriptionTranslationKey: 'page.actions.connectMcp.copyConfigDescription',
69
63
  descriptionDefault: 'Copy MCP JSON Configuration',
70
64
  },
71
- ];
65
+ };
72
66
 
73
67
  // Copy actions keep the dropdown open so the copied checkmark is visible.
74
68
  const COPY_OPTIONS: MCPOption[] = ['claude-code', 'codex', 'copy'];
@@ -89,7 +83,7 @@ export function ConnectMCPButton({
89
83
 
90
84
  const { copiedOption, visibleOptions, handleAction } = useConnectMCPButton({ options });
91
85
 
92
- const menuOptions = MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key));
86
+ const menuOptions = visibleOptions.map((key) => ({ key, ...MENU_OPTIONS[key] }));
93
87
  const primaryOption = menuOptions[0];
94
88
  const dropdownOptions = menuOptions.slice(1);
95
89
 
@@ -3,5 +3,7 @@ import type { CatalogEntityRelationsGraphProps } from '@redocly/theme/components
3
3
  import { dynamic } from '@redocly/theme/core/utils';
4
4
 
5
5
  export const CatalogEntityRelationsGraph = dynamic<CatalogEntityRelationsGraphProps>(() =>
6
- import('./CatalogEntityRelationsGraph').then((mod) => mod.CatalogEntityRelationsGraph),
6
+ import('@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph').then(
7
+ (mod) => mod.CatalogEntityRelationsGraph,
8
+ ),
7
9
  );
@@ -1,4 +1,4 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -19,6 +19,7 @@ export function CatalogFilterDateRange({
19
19
  filter,
20
20
  className,
21
21
  }: CatalogFilterDateRangeProps): JSX.Element {
22
+ const [today] = useState(() => new Date());
22
23
  const { useTranslate } = useThemeHooks();
23
24
  const { translate } = useTranslate();
24
25
 
@@ -60,7 +61,7 @@ export function CatalogFilterDateRange({
60
61
  monthPlaceholder="MM"
61
62
  yearPlaceholder="YYYY"
62
63
  value={from}
63
- maxDate={new Date()}
64
+ maxDate={today}
64
65
  onChange={(from) => {
65
66
  if (Array.isArray(from)) return;
66
67
  filter.selectOption({
@@ -11,9 +11,10 @@ import { createPortal } from 'react-dom';
11
11
  import styled, { css } from 'styled-components';
12
12
 
13
13
  import type { CSSProperties, ReactElement } from 'react';
14
- import type { RenderableChildren } from '@redocly/theme/core/types';
14
+ import type { DropdownAlignment, RenderableChildren } from '@redocly/theme/core/types';
15
15
 
16
16
  import { useOutsideClick, useControlledState } from '@redocly/theme/core/hooks';
17
+ import { fitDropdownAlignment } from '@redocly/theme/core/utils';
17
18
  import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
18
19
  import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
19
20
 
@@ -37,7 +38,7 @@ export type DropdownProps = {
37
38
  children: RenderableChildren<DropdownMenuContext>;
38
39
  triggerEvent?: 'click' | 'hover';
39
40
  placement?: 'top' | 'bottom';
40
- alignment?: 'start' | 'end';
41
+ alignment?: DropdownAlignment;
41
42
 
42
43
  active?: boolean;
43
44
  closeOnClick?: boolean;
@@ -46,6 +47,8 @@ export type DropdownProps = {
46
47
  className?: string;
47
48
  withArrow?: boolean;
48
49
  portalled?: boolean;
50
+ /** Element the menu lines up with when it is not the dropdown itself, e.g. a whole split button. */
51
+ alignmentAnchorRef?: React.RefObject<HTMLElement | null>;
49
52
 
50
53
  onClick?: (event: React.UIEvent) => void;
51
54
  onClose?: () => void;
@@ -66,6 +69,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
66
69
  placement,
67
70
  alignment,
68
71
  portalled = false,
72
+ alignmentAnchorRef,
69
73
  onClick,
70
74
  onClose,
71
75
  onOpen,
@@ -76,6 +80,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
76
80
  const menuRef = useRef<HTMLDivElement | null>(null);
77
81
  const [isOpen, setIsOpen] = useControlledState<boolean>(false, active);
78
82
  const [menuStyle, setMenuStyle] = useState<CSSProperties>({});
83
+ const [fittedAlignment, setFittedAlignment] = useState(alignment);
79
84
 
80
85
  const wrapperRef = (ref as React.RefObject<HTMLDivElement | null>) || dropdownRef;
81
86
 
@@ -115,7 +120,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
115
120
  const updateMenuPosition = useCallback(() => {
116
121
  const wrapper = wrapperRef.current;
117
122
  if (!wrapper) return;
118
- const rect = wrapper.getBoundingClientRect();
123
+ const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
119
124
  const menuHeight = menuRef.current?.offsetHeight ?? 0;
120
125
  const fitsBelow = rect.bottom + menuHeight <= window.innerHeight;
121
126
  const fitsAbove = rect.top - menuHeight >= 0;
@@ -126,7 +131,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
126
131
  } else {
127
132
  style.top = rect.bottom;
128
133
  }
129
- if (alignment === 'end') {
134
+ if (fitDropdownAlignment(rect, menuRef.current?.offsetWidth ?? 0, alignment) === 'end') {
130
135
  style.right = window.innerWidth - rect.right;
131
136
  } else {
132
137
  style.left = rect.left;
@@ -136,7 +141,24 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
136
141
  style.zIndex = zIndex;
137
142
  }
138
143
  setMenuStyle(style);
139
- }, [placement, alignment, wrapperRef]);
144
+ }, [placement, alignment, wrapperRef, alignmentAnchorRef]);
145
+
146
+ /* The absolute-positioned menu is placed by CSS, so the side is measured once it is
147
+ shown and again on resize; a side that runs off the viewport flips to the other one. */
148
+ const updateFittedAlignment = useCallback(() => {
149
+ const wrapper = wrapperRef.current;
150
+ const menu = menuRef.current;
151
+ if (!wrapper || !menu) return;
152
+ const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
153
+ setFittedAlignment(fitDropdownAlignment(rect, menu.offsetWidth, alignment));
154
+ }, [alignment, wrapperRef, alignmentAnchorRef]);
155
+
156
+ useIsomorphicLayoutEffect(() => {
157
+ if (portalled || !isOpen) return;
158
+ updateFittedAlignment();
159
+ window.addEventListener('resize', updateFittedAlignment);
160
+ return () => window.removeEventListener('resize', updateFittedAlignment);
161
+ }, [portalled, isOpen, updateFittedAlignment]);
140
162
 
141
163
  useIsomorphicLayoutEffect(() => {
142
164
  if (!portalled || !isOpen) return;
@@ -172,7 +194,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
172
194
  <ChildrenWrapper
173
195
  ref={menuRef}
174
196
  $placement={placement}
175
- $alignment={alignment}
197
+ $alignment={fittedAlignment}
176
198
  $isOpen={isOpen}
177
199
  $portalled={portalled}
178
200
  style={portalled ? menuStyle : undefined}
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -11,6 +11,7 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
11
11
  import { formatDateWithoutTimeZone } from '@redocly/theme/core/utils';
12
12
 
13
13
  export function FilterDateRange({ filter }: FilterProps): JSX.Element {
14
+ const [today] = useState(() => new Date());
14
15
  const { useTranslate } = useThemeHooks();
15
16
  const { translate } = useTranslate();
16
17
 
@@ -33,7 +34,7 @@ export function FilterDateRange({ filter }: FilterProps): JSX.Element {
33
34
  monthPlaceholder="MM"
34
35
  yearPlaceholder="YYYY"
35
36
  value={from}
36
- maxDate={new Date()}
37
+ maxDate={today}
37
38
  onChange={(from) => {
38
39
  if (Array.isArray(from)) return;
39
40
  filter.selectOption({
@@ -1,9 +1,10 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
 
4
4
  import type { PropsWithChildren, JSX } from 'react';
5
5
 
6
6
  import { IS_BROWSER } from '@redocly/theme/core/utils';
7
+ import { PortalContainerContext } from '@redocly/theme/core/contexts/PortalContainerContext';
7
8
 
8
9
  export type PortalProps = {
9
10
  mountId?: string;
@@ -13,7 +14,8 @@ export function Portal({
13
14
  children,
14
15
  mountId = 'root',
15
16
  }: PropsWithChildren<PortalProps>): React.ReactPortal | JSX.Element {
16
- const mount = IS_BROWSER ? document.getElementById(mountId) || document.body : null;
17
+ const container = useContext(PortalContainerContext);
18
+ const mount = IS_BROWSER ? container || document.getElementById(mountId) || document.body : null;
17
19
 
18
20
  if (!mount) {
19
21
  return <>{children}</>;
@@ -10,6 +10,8 @@ import { ThumbUpFilledIcon } from '@redocly/theme/icons/ThumbUpFilledIcon/ThumbU
10
10
  import { ThumbDownIcon } from '@redocly/theme/icons/ThumbDownIcon/ThumbDownIcon';
11
11
  import { ThumbDownFilledIcon } from '@redocly/theme/icons/ThumbDownFilledIcon/ThumbDownFilledIcon';
12
12
  import { CopyButton } from '@redocly/theme/components/Buttons/CopyButton';
13
+ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
14
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
13
15
 
14
16
  export type SearchAiActionButtonsProps = {
15
17
  content: string;
@@ -26,28 +28,38 @@ export function SearchAiActionButtons({
26
28
  onFeedback,
27
29
  disabled,
28
30
  }: SearchAiActionButtonsProps): JSX.Element {
31
+ const { useTranslate } = useThemeHooks();
32
+ const { translate } = useTranslate();
33
+
29
34
  return (
30
35
  <ActionButtonsWrapper className={className} data-component-name="Search/SearchAiActionButtons">
31
- <CopyButton data={content} />
32
- <FeedbackButton
33
- variant="text"
34
- size="small"
35
- icon={feedback === FeedbackType.Like ? <ThumbUpFilledIcon /> : <ThumbUpIcon />}
36
- onClick={() => !disabled && onFeedback(FeedbackType.Like)}
37
- extraClass={feedback === FeedbackType.Like ? 'active' : ''}
38
- aria-label="Like this response"
39
- disabled={disabled}
40
- />
36
+ <Tooltip tip={translate('search.ai.actions.copy', 'Copy the response')} placement="top">
37
+ <CopyButton data={content} />
38
+ </Tooltip>
39
+
40
+ <Tooltip tip={translate('search.ai.actions.like', 'Like the response')} placement="top">
41
+ <FeedbackButton
42
+ variant="text"
43
+ size="small"
44
+ icon={feedback === FeedbackType.Like ? <ThumbUpFilledIcon /> : <ThumbUpIcon />}
45
+ onClick={() => !disabled && onFeedback(FeedbackType.Like)}
46
+ extraClass={feedback === FeedbackType.Like ? 'active' : ''}
47
+ aria-label="Like this response"
48
+ disabled={disabled}
49
+ />
50
+ </Tooltip>
41
51
 
42
- <FeedbackButton
43
- variant="text"
44
- size="small"
45
- icon={feedback === FeedbackType.Dislike ? <ThumbDownFilledIcon /> : <ThumbDownIcon />}
46
- onClick={() => !disabled && onFeedback(FeedbackType.Dislike)}
47
- extraClass={feedback === FeedbackType.Dislike ? 'active' : ''}
48
- aria-label="Dislike this response"
49
- disabled={disabled}
50
- />
52
+ <Tooltip tip={translate('search.ai.actions.dislike', 'Dislike the response')} placement="top">
53
+ <FeedbackButton
54
+ variant="text"
55
+ size="small"
56
+ icon={feedback === FeedbackType.Dislike ? <ThumbDownFilledIcon /> : <ThumbDownIcon />}
57
+ onClick={() => !disabled && onFeedback(FeedbackType.Dislike)}
58
+ extraClass={feedback === FeedbackType.Dislike ? 'active' : ''}
59
+ aria-label="Dislike this response"
60
+ disabled={disabled}
61
+ />
62
+ </Tooltip>
51
63
  </ActionButtonsWrapper>
52
64
  );
53
65
  }