@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.
- package/lib/components/Buttons/ConnectMCPButton.js +8 -13
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
- package/lib/components/Dropdown/Dropdown.d.ts +4 -2
- package/lib/components/Dropdown/Dropdown.js +28 -8
- package/lib/components/Filter/FilterDateRange.js +36 -2
- package/lib/components/Portal/Portal.js +37 -5
- package/lib/components/Search/SearchAiActionButtons.js +8 -3
- package/lib/components/Search/SearchAiActivity.d.ts +10 -0
- package/lib/components/Search/SearchAiActivity.js +278 -0
- package/lib/components/Search/SearchAiDialog.js +5 -1
- package/lib/components/Search/SearchAiMessage.d.ts +2 -1
- package/lib/components/Search/SearchAiMessage.js +20 -108
- package/lib/components/Search/SearchDialog.js +10 -0
- package/lib/components/Search/SearchItem.js +1 -8
- package/lib/components/Search/variables.js +15 -2
- package/lib/components/Select/SelectInput.js +2 -8
- package/lib/components/SplitButton/SplitButton.js +37 -3
- package/lib/core/constants/mcp.d.ts +2 -2
- package/lib/core/constants/mcp.js +3 -8
- package/lib/core/constants/search.js +5 -1
- package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
- package/lib/core/contexts/PortalContainerContext.js +7 -0
- package/lib/core/hooks/index.d.ts +2 -0
- package/lib/core/hooks/index.js +2 -0
- package/lib/core/hooks/menu/use-collapse.js +1 -1
- package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
- package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
- package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
- package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
- package/lib/core/hooks/use-connect-mcp-button.js +2 -1
- package/lib/core/hooks/use-full-height.d.ts +1 -1
- package/lib/core/hooks/use-full-height.js +8 -8
- package/lib/core/styles/global.d.ts +2 -0
- package/lib/core/styles/global.js +80 -76
- package/lib/core/styles/index.d.ts +1 -0
- package/lib/core/styles/index.js +3 -1
- package/lib/core/styles/search-ai-appear.d.ts +1 -0
- package/lib/core/styles/search-ai-appear.js +18 -0
- package/lib/core/types/dropdown.d.ts +1 -0
- package/lib/core/types/dropdown.js +3 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +2 -1
- package/lib/core/types/search.d.ts +23 -0
- package/lib/core/utils/assign-ref.d.ts +2 -0
- package/lib/core/utils/assign-ref.js +12 -0
- package/lib/core/utils/content-segments.d.ts +12 -1
- package/lib/core/utils/content-segments.js +90 -5
- package/lib/core/utils/dropdown.d.ts +2 -0
- package/lib/core/utils/dropdown.js +12 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.d.ts +2 -1
- package/lib/core/utils/mcp.js +5 -0
- package/lib/core/utils/search-ai-activity.d.ts +4 -0
- package/lib/core/utils/search-ai-activity.js +83 -0
- package/lib/markdoc/tags/connect-mcp.js +15 -0
- package/package.json +3 -2
- package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
- package/src/components/Dropdown/Dropdown.tsx +28 -6
- package/src/components/Filter/FilterDateRange.tsx +3 -2
- package/src/components/Portal/Portal.tsx +4 -2
- package/src/components/Search/SearchAiActionButtons.tsx +31 -19
- package/src/components/Search/SearchAiActivity.tsx +318 -0
- package/src/components/Search/SearchAiDialog.tsx +4 -3
- package/src/components/Search/SearchAiMessage.tsx +27 -135
- package/src/components/Search/SearchDialog.tsx +11 -0
- package/src/components/Search/SearchItem.tsx +2 -9
- package/src/components/Search/variables.ts +15 -2
- package/src/components/Select/SelectInput.tsx +2 -8
- package/src/components/SplitButton/SplitButton.tsx +8 -2
- package/src/core/constants/mcp.ts +3 -9
- package/src/core/constants/search.ts +5 -1
- package/src/core/contexts/PortalContainerContext.tsx +4 -0
- package/src/core/hooks/__mocks__/index.ts +2 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/menu/use-collapse.ts +1 -1
- package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
- package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
- package/src/core/hooks/use-connect-mcp-button.ts +7 -2
- package/src/core/hooks/use-full-height.ts +8 -8
- package/src/core/styles/global.ts +80 -75
- package/src/core/styles/index.ts +2 -1
- package/src/core/styles/search-ai-appear.ts +16 -0
- package/src/core/types/dropdown.ts +1 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +4 -1
- package/src/core/types/mcp.ts +3 -1
- package/src/core/types/search.ts +21 -0
- package/src/core/utils/assign-ref.ts +9 -0
- package/src/core/utils/content-segments.ts +98 -6
- package/src/core/utils/dropdown.ts +14 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +7 -1
- package/src/core/utils/search-ai-activity.ts +110 -0
- 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.
|
|
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.
|
|
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 =
|
|
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
|
|
package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx
CHANGED
|
@@ -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('
|
|
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={
|
|
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?:
|
|
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={
|
|
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={
|
|
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
|
|
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
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
}
|