@redocly/theme 0.67.0 → 0.68.0-next.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.
- package/lib/components/Buttons/ConnectMCPButton.d.ts +1 -1
- package/lib/components/Buttons/ConnectMCPButton.js +50 -17
- package/lib/components/Buttons/variables.js +3 -0
- package/lib/components/Feedback/Reasons.js +2 -3
- package/lib/components/Menu/MenuItem.js +2 -1
- package/lib/components/Search/SearchAiDialog.js +2 -1
- package/lib/components/Search/SearchAiResponse.js +2 -1
- package/lib/components/Search/SearchTrigger.js +3 -3
- package/lib/core/constants/common.d.ts +1 -0
- package/lib/core/constants/common.js +2 -1
- package/lib/core/constants/search.d.ts +1 -0
- package/lib/core/constants/search.js +2 -1
- package/lib/core/hooks/search/use-search-dialog.js +2 -1
- package/package.json +3 -3
- package/src/components/Buttons/ConnectMCPButton.tsx +110 -63
- package/src/components/Buttons/variables.ts +3 -0
- package/src/components/Feedback/Reasons.tsx +2 -4
- package/src/components/Menu/MenuItem.tsx +5 -2
- package/src/components/Search/SearchAiDialog.tsx +1 -1
- package/src/components/Search/SearchAiResponse.tsx +1 -1
- package/src/components/Search/SearchTrigger.tsx +3 -3
- package/src/core/constants/common.ts +2 -0
- package/src/core/constants/search.ts +2 -0
- package/src/core/hooks/search/use-search-dialog.ts +2 -1
|
@@ -5,4 +5,4 @@ export type ConnectMCPButtonProps = {
|
|
|
5
5
|
alignment?: 'start' | 'end';
|
|
6
6
|
options?: MCPOption[];
|
|
7
7
|
};
|
|
8
|
-
export declare function ConnectMCPButton({ placement, alignment, options }: ConnectMCPButtonProps): JSX.Element;
|
|
8
|
+
export declare function ConnectMCPButton({ placement, alignment, options }: ConnectMCPButtonProps): JSX.Element | null;
|
|
@@ -39,7 +39,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.ConnectMCPButton = ConnectMCPButton;
|
|
40
40
|
const react_1 = __importStar(require("react"));
|
|
41
41
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
42
|
-
const Dropdown_1 = require("../../components/Dropdown/Dropdown");
|
|
43
42
|
const DropdownMenu_1 = require("../../components/Dropdown/DropdownMenu");
|
|
44
43
|
const DropdownMenuItem_1 = require("../../components/Dropdown/DropdownMenuItem");
|
|
45
44
|
const Button_1 = require("../../components/Button/Button");
|
|
@@ -47,12 +46,9 @@ const CursorIcon_1 = require("../../icons/CursorIcon/CursorIcon");
|
|
|
47
46
|
const VSCodeIcon_1 = require("../../icons/VSCodeIcon/VSCodeIcon");
|
|
48
47
|
const CopyIcon_1 = require("../../icons/CopyIcon/CopyIcon");
|
|
49
48
|
const CheckmarkFilledIcon_1 = require("../../icons/CheckmarkFilledIcon/CheckmarkFilledIcon");
|
|
50
|
-
const
|
|
49
|
+
const ChevronDownIcon_1 = require("../../icons/ChevronDownIcon/ChevronDownIcon");
|
|
50
|
+
const ChevronUpIcon_1 = require("../../icons/ChevronUpIcon/ChevronUpIcon");
|
|
51
51
|
const hooks_1 = require("../../core/hooks");
|
|
52
|
-
const hooks_2 = require("../../core/hooks");
|
|
53
|
-
const TriggerButton = react_1.default.memo(({ text }) => {
|
|
54
|
-
return (react_1.default.createElement(StyledButton, { variant: "secondary", icon: react_1.default.createElement(ConnectIcon_1.ConnectIcon, null) }, text));
|
|
55
|
-
});
|
|
56
52
|
const MENU_OPTIONS = [
|
|
57
53
|
{
|
|
58
54
|
key: 'cursor',
|
|
@@ -80,31 +76,68 @@ const MENU_OPTIONS = [
|
|
|
80
76
|
},
|
|
81
77
|
];
|
|
82
78
|
function ConnectMCPButton({ placement = 'bottom', alignment = 'end', options = ['cursor', 'vscode', 'copy'], }) {
|
|
83
|
-
const { useTranslate } = (0,
|
|
79
|
+
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
84
80
|
const { translate } = useTranslate();
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
82
|
+
const wrapperRef = (0, react_1.useRef)(null);
|
|
83
|
+
const { isCopied, visibleOptions, handleAction } = (0, hooks_1.useConnectMCPButton)({ options });
|
|
84
|
+
(0, hooks_1.useOutsideClick)(wrapperRef, () => setIsOpen(false));
|
|
85
|
+
const menuOptions = MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key));
|
|
86
|
+
const primaryOption = menuOptions[0];
|
|
87
|
+
const dropdownOptions = menuOptions.slice(1);
|
|
88
|
+
if (!primaryOption)
|
|
89
|
+
return null;
|
|
90
|
+
const PrimaryIcon = primaryOption.icon;
|
|
91
|
+
// When copy is the primary action there is no dropdown item to show the copied state, so the
|
|
92
|
+
// primary button itself must reflect it.
|
|
93
|
+
const showPrimaryCopied = primaryOption.key === 'copy' && isCopied;
|
|
94
|
+
const handleMenuAction = (key) => {
|
|
95
|
+
handleAction(key);
|
|
96
|
+
setIsOpen(false);
|
|
97
|
+
};
|
|
98
|
+
return (react_1.default.createElement(ConnectMCPButtonWrapper, { ref: wrapperRef, "data-component-name": "Buttons/ConnectMCPButton" },
|
|
99
|
+
react_1.default.createElement(SplitButtonGroup, null,
|
|
100
|
+
react_1.default.createElement(PrimaryButton, { variant: "secondary", icon: showPrimaryCopied ? (react_1.default.createElement(CheckmarkFilledIcon_1.CheckmarkFilledIcon, { color: "var(--color-success-base)" })) : (react_1.default.createElement(PrimaryIcon, null)), onClick: () => handleAction(primaryOption.key) }, translate(primaryOption.titleTranslationKey, primaryOption.titleDefault)),
|
|
101
|
+
dropdownOptions.length > 0 && (react_1.default.createElement(ChevronButton, { variant: "secondary", "aria-label": translate('page.actions.connectMcp.moreOptions', 'More options'), "aria-expanded": isOpen, "aria-haspopup": "menu", onClick: () => setIsOpen((prev) => !prev) }, isOpen ? react_1.default.createElement(ChevronUpIcon_1.ChevronUpIcon, null) : react_1.default.createElement(ChevronDownIcon_1.ChevronDownIcon, null)))),
|
|
102
|
+
isOpen && dropdownOptions.length > 0 && (react_1.default.createElement(DropdownWrapper, { $placement: placement, $alignment: alignment },
|
|
103
|
+
react_1.default.createElement(DropdownMenu_1.DropdownMenu, null, dropdownOptions.map((option) => {
|
|
92
104
|
const Icon = option.icon;
|
|
93
105
|
const showCheckmark = option.key === 'copy' && isCopied;
|
|
94
|
-
return (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { key: option.key, onAction: () =>
|
|
106
|
+
return (react_1.default.createElement(DropdownMenuItem_1.DropdownMenuItem, { key: option.key, onAction: () => handleMenuAction(option.key) },
|
|
95
107
|
react_1.default.createElement(MenuItemContent, null,
|
|
96
108
|
react_1.default.createElement(MenuItemIcon, null, showCheckmark ? (react_1.default.createElement(CheckmarkFilledIcon_1.CheckmarkFilledIcon, { color: "var(--color-success-base)" })) : (react_1.default.createElement(Icon, null))),
|
|
97
109
|
react_1.default.createElement(MenuItemText, null,
|
|
98
110
|
react_1.default.createElement(MenuItemTitle, null, translate(option.titleTranslationKey, option.titleDefault)),
|
|
99
111
|
react_1.default.createElement(MenuItemDescription, null, translate(option.descriptionTranslationKey, option.descriptionDefault))))));
|
|
100
|
-
})))));
|
|
112
|
+
}))))));
|
|
101
113
|
}
|
|
102
114
|
const ConnectMCPButtonWrapper = styled_components_1.default.div `
|
|
103
115
|
display: inline-block;
|
|
104
116
|
position: relative;
|
|
105
117
|
`;
|
|
106
|
-
const
|
|
118
|
+
const SplitButtonGroup = styled_components_1.default.div `
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
align-items: stretch;
|
|
121
|
+
`;
|
|
122
|
+
const PrimaryButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
107
123
|
--button-gap: var(--connect-mcp-button-gap);
|
|
124
|
+
border-top-right-radius: 0;
|
|
125
|
+
border-bottom-right-radius: 0;
|
|
126
|
+
`;
|
|
127
|
+
const ChevronButton = (0, styled_components_1.default)(Button_1.Button) `
|
|
128
|
+
border-top-left-radius: 0;
|
|
129
|
+
border-bottom-left-radius: 0;
|
|
130
|
+
border-left: none;
|
|
131
|
+
padding-inline: var(--connect-mcp-button-chevron-padding-inline);
|
|
132
|
+
`;
|
|
133
|
+
const DropdownWrapper = styled_components_1.default.div `
|
|
134
|
+
position: absolute;
|
|
135
|
+
top: ${({ $placement }) => ($placement === 'top' ? 'auto' : '100%')};
|
|
136
|
+
bottom: ${({ $placement }) => ($placement === 'top' ? '100%' : 'auto')};
|
|
137
|
+
left: ${({ $alignment }) => ($alignment === 'start' ? '0' : 'auto')};
|
|
138
|
+
right: ${({ $alignment }) => ($alignment === 'end' ? '0' : 'auto')};
|
|
139
|
+
padding-top: var(--dropdown-menu-padding-top);
|
|
140
|
+
z-index: var(--dropdown-menu-z-index);
|
|
108
141
|
`;
|
|
109
142
|
const MenuItemContent = styled_components_1.default.div `
|
|
110
143
|
display: flex;
|
|
@@ -59,6 +59,9 @@ exports.connectMCPButton = (0, styled_components_1.css) `
|
|
|
59
59
|
/* Button gap */
|
|
60
60
|
--connect-mcp-button-gap: var(--spacing-xs);
|
|
61
61
|
|
|
62
|
+
/* Chevron button */
|
|
63
|
+
--connect-mcp-button-chevron-padding-inline: var(--spacing-xs);
|
|
64
|
+
|
|
62
65
|
/* Menu item layout */
|
|
63
66
|
--connect-mcp-button-menu-item-gap: var(--spacing-sm);
|
|
64
67
|
--connect-mcp-button-menu-item-padding-block: var(--spacing-xxs);
|
|
@@ -41,14 +41,13 @@ const React = __importStar(require("react"));
|
|
|
41
41
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
42
42
|
const hooks_1 = require("../../core/hooks");
|
|
43
43
|
function Reasons({ settings, onChange, className }) {
|
|
44
|
-
const { label, component, items = [] } = settings;
|
|
44
|
+
const { label, component = 'checkbox', items = [] } = settings;
|
|
45
45
|
const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
|
|
46
46
|
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
47
47
|
const { translate } = useTranslate();
|
|
48
48
|
if (!items.length) {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
const input_type = component || 'checkbox';
|
|
52
51
|
const handleOptionChange = (position) => {
|
|
53
52
|
const updatedCheckedState = component === 'checkbox'
|
|
54
53
|
? checkedState.map((item, index) => (index === position ? !item : item))
|
|
@@ -60,7 +59,7 @@ function Reasons({ settings, onChange, className }) {
|
|
|
60
59
|
React.createElement(Label, null, label ||
|
|
61
60
|
translate('feedback.settings.reasons.label', 'Which statement describes your thoughts about this page?')),
|
|
62
61
|
items.map((reason, idx) => (React.createElement(OptionWrapper, { key: reason },
|
|
63
|
-
React.createElement("input", { type:
|
|
62
|
+
React.createElement("input", { type: component, value: reason, checked: checkedState[idx], name: "reasons", onChange: () => handleOptionChange(idx) }),
|
|
64
63
|
React.createElement("label", { "data-translation-key": `feedback.settings.reasons.items.${idx + 1}`, id: reason, onClick: () => handleOptionChange(idx) }, translate(`feedback.settings.reasons.items.${idx + 1}`, reason)))))));
|
|
65
64
|
}
|
|
66
65
|
const ReasonsWrapper = styled_components_1.default.div `
|
|
@@ -49,6 +49,7 @@ const GenericIcon_1 = require("../../icons/GenericIcon/GenericIcon");
|
|
|
49
49
|
const Tag_1 = require("../../components/Tag/Tag");
|
|
50
50
|
function MenuItem(props) {
|
|
51
51
|
var _a, _b;
|
|
52
|
+
var _c;
|
|
52
53
|
const { item, depth, className, onClick } = props;
|
|
53
54
|
const { useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)();
|
|
54
55
|
const { translate } = useTranslate();
|
|
@@ -101,7 +102,7 @@ function MenuItem(props) {
|
|
|
101
102
|
return (react_1.default.createElement(MenuItemWrapper, { "data-component-name": "Menu/MenuItem", className: generateClassName({ type, item, className }), role: "listitem" },
|
|
102
103
|
item.link ? (react_1.default.createElement(MenuItemLink, { to: item.link, external: item.external, target: item.target, languageInsensitive: item.languageInsensitive, onKeyDown: handleExpandOnEnter }, label)) : (label),
|
|
103
104
|
isNested ? (react_1.default.createElement(MenuItemNestedWrapper, { $isExpanded: isExpanded, $depth: depth, ref: nestedMenuRef, style: style }, isExpanded || !canUnmount ? props.children : null)) : null,
|
|
104
|
-
item.separatorLine ? (react_1.default.createElement(MenuItemSeparatorLine, { $depth: depth, $linePosition: item.linePosition })) : null));
|
|
105
|
+
item.separatorLine ? (react_1.default.createElement(MenuItemSeparatorLine, { $depth: depth, $linePosition: (_c = item.linePosition) !== null && _c !== void 0 ? _c : constants_1.DEFAULT_LINE_POSITION })) : null));
|
|
105
106
|
}
|
|
106
107
|
/* for backward compatibility */
|
|
107
108
|
function isDirectColorValue(color) {
|
|
@@ -47,11 +47,12 @@ const SearchAiMessage_1 = require("../../components/Search/SearchAiMessage");
|
|
|
47
47
|
const Admonition_1 = require("../../components/Admonition/Admonition");
|
|
48
48
|
const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
|
|
49
49
|
function SearchAiDialog({ isGeneratingResponse, response, initialMessage, error, resources, onMessageSent, className, conversation, setConversation, toolCalls = [], contentSegments, }) {
|
|
50
|
+
var _a;
|
|
50
51
|
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
51
52
|
const { aiAssistant } = (0, hooks_1.useThemeConfig)();
|
|
52
53
|
const { translate } = useTranslate();
|
|
53
54
|
const conversationEndRef = react_1.default.useRef(null);
|
|
54
|
-
const suggestions = aiAssistant === null || aiAssistant === void 0 ? void 0 : aiAssistant.suggestions;
|
|
55
|
+
const suggestions = (_a = aiAssistant === null || aiAssistant === void 0 ? void 0 : aiAssistant.suggestions) !== null && _a !== void 0 ? _a : [];
|
|
55
56
|
const placeholder = isGeneratingResponse
|
|
56
57
|
? translate('search.ai.generatingResponse', 'Generating response...')
|
|
57
58
|
: conversation.length > 0
|
|
@@ -19,6 +19,7 @@ const ErrorFilledIcon_1 = require("../../icons/ErrorFilledIcon/ErrorFilledIcon")
|
|
|
19
19
|
const constants_1 = require("../../core/constants");
|
|
20
20
|
const ChatIcon_1 = require("../../icons/ChatIcon/ChatIcon");
|
|
21
21
|
function SearchAiResponse(props) {
|
|
22
|
+
var _a;
|
|
22
23
|
const { useMarkdownText } = (0, hooks_1.useThemeHooks)();
|
|
23
24
|
const { question, response, isGeneratingResponse, resources, error, onSuggestionClick } = props;
|
|
24
25
|
const { aiAssistant } = (0, hooks_1.useThemeConfig)();
|
|
@@ -26,7 +27,7 @@ function SearchAiResponse(props) {
|
|
|
26
27
|
const { translate } = useTranslate();
|
|
27
28
|
const markdownResponse = useMarkdownText(response || '');
|
|
28
29
|
let responseContainer = null;
|
|
29
|
-
const suggestions = aiAssistant === null || aiAssistant === void 0 ? void 0 : aiAssistant.suggestions;
|
|
30
|
+
const suggestions = (_a = aiAssistant === null || aiAssistant === void 0 ? void 0 : aiAssistant.suggestions) !== null && _a !== void 0 ? _a : [];
|
|
30
31
|
const hasPendingOrReceivedResponse = response || isGeneratingResponse || error;
|
|
31
32
|
if (hasPendingOrReceivedResponse) {
|
|
32
33
|
let icon;
|
|
@@ -43,14 +43,14 @@ const hooks_1 = require("../../core/hooks");
|
|
|
43
43
|
const Button_1 = require("../../components/Button/Button");
|
|
44
44
|
const SearchIcon_1 = require("../../icons/SearchIcon/SearchIcon");
|
|
45
45
|
const utils_1 = require("../../core/utils");
|
|
46
|
+
const constants_1 = require("../../core/constants");
|
|
46
47
|
function SearchTrigger({ onClick, className }) {
|
|
47
48
|
var _a;
|
|
48
49
|
var _b;
|
|
49
50
|
const themeSettings = (0, hooks_1.useThemeConfig)();
|
|
50
51
|
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
51
52
|
const { translate } = useTranslate();
|
|
52
|
-
const
|
|
53
|
-
const keyShortcuts = (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : [defaultKeyShortcut];
|
|
53
|
+
const keyShortcuts = (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : [constants_1.DEFAULT_KEYBOARD_SHORTCUTS];
|
|
54
54
|
let mainShortcutKey = null;
|
|
55
55
|
if (keyShortcuts) {
|
|
56
56
|
if (Array.isArray(keyShortcuts)) {
|
|
@@ -67,7 +67,7 @@ function SearchTrigger({ onClick, className }) {
|
|
|
67
67
|
React.createElement(SearchTriggerInput, { "data-testid": "search-trigger-input", "data-translation-key": "search.navbar.label" },
|
|
68
68
|
React.createElement(SearchIcon_1.SearchIcon, null),
|
|
69
69
|
translate('search.navbar.label', 'Search'),
|
|
70
|
-
mainShortcutKey ===
|
|
70
|
+
mainShortcutKey === constants_1.DEFAULT_KEYBOARD_SHORTCUTS ? (React.createElement("span", null, isMac ? '⌘K' : 'Ctrl+K')) : (React.createElement("span", null, mainShortcutKey)))));
|
|
71
71
|
}
|
|
72
72
|
const SearchTriggerWrapper = styled_components_1.default.div `
|
|
73
73
|
color: var(--search-trigger-color);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_COLOR_MODES = exports.MobileMenuType = exports.MenuItemType = exports.DEFAULT_FEEDBACK_TYPE = exports.FEEDBACK_TYPES = exports.ICONS_CDN_URL = exports.CONTENT_ID = exports.DEFAULT_LOCALE_PLACEHOLDER = exports.REDOCLY_TEAMS_RBAC = void 0;
|
|
3
|
+
exports.DEFAULT_LINE_POSITION = exports.DEFAULT_COLOR_MODES = exports.MobileMenuType = exports.MenuItemType = exports.DEFAULT_FEEDBACK_TYPE = exports.FEEDBACK_TYPES = exports.ICONS_CDN_URL = exports.CONTENT_ID = exports.DEFAULT_LOCALE_PLACEHOLDER = exports.REDOCLY_TEAMS_RBAC = void 0;
|
|
4
4
|
var config_1 = require("@redocly/config");
|
|
5
5
|
Object.defineProperty(exports, "REDOCLY_TEAMS_RBAC", { enumerable: true, get: function () { return config_1.REDOCLY_TEAMS_RBAC; } });
|
|
6
6
|
exports.DEFAULT_LOCALE_PLACEHOLDER = 'default_locale';
|
|
@@ -37,4 +37,5 @@ exports.DEFAULT_COLOR_MODES = {
|
|
|
37
37
|
LIGHT: 'light',
|
|
38
38
|
DARK: 'dark',
|
|
39
39
|
};
|
|
40
|
+
exports.DEFAULT_LINE_POSITION = 'top';
|
|
40
41
|
//# sourceMappingURL=common.js.map
|
|
@@ -14,6 +14,7 @@ export type AiSearchConversationRole = (typeof AiSearchConversationRole)[keyof t
|
|
|
14
14
|
export declare const AI_SEARCH_ERROR_CONFIG: Record<AiSearchError, AiSearchErrorConfig>;
|
|
15
15
|
export declare const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
|
|
16
16
|
export declare const SEARCH_DEBOUNCE_TIME_MS = 300;
|
|
17
|
+
export declare const DEFAULT_KEYBOARD_SHORTCUTS = "\u2318+K,CTRL+K";
|
|
17
18
|
export declare const TOOL_CALL_DISPLAY_TEXT: Partial<Record<string, {
|
|
18
19
|
inProgressText: string;
|
|
19
20
|
completedText: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TOOL_CALL_DISPLAY_TEXT = exports.SEARCH_DEBOUNCE_TIME_MS = exports.AI_SEARCH_MAX_MESSAGE_LENGTH = exports.AI_SEARCH_ERROR_CONFIG = exports.AiSearchConversationRole = exports.AiSearchError = void 0;
|
|
3
|
+
exports.TOOL_CALL_DISPLAY_TEXT = exports.DEFAULT_KEYBOARD_SHORTCUTS = exports.SEARCH_DEBOUNCE_TIME_MS = exports.AI_SEARCH_MAX_MESSAGE_LENGTH = exports.AI_SEARCH_ERROR_CONFIG = exports.AiSearchConversationRole = exports.AiSearchError = void 0;
|
|
4
4
|
const search_1 = require("../types/search");
|
|
5
5
|
var AiSearchError;
|
|
6
6
|
(function (AiSearchError) {
|
|
@@ -39,6 +39,7 @@ exports.AI_SEARCH_ERROR_CONFIG = {
|
|
|
39
39
|
};
|
|
40
40
|
exports.AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
|
|
41
41
|
exports.SEARCH_DEBOUNCE_TIME_MS = 300;
|
|
42
|
+
exports.DEFAULT_KEYBOARD_SHORTCUTS = '⌘+K,CTRL+K';
|
|
42
43
|
exports.TOOL_CALL_DISPLAY_TEXT = {
|
|
43
44
|
[search_1.ToolCallName.SearchDocumentation]: {
|
|
44
45
|
inProgressText: 'Searching documentation...',
|
|
@@ -11,6 +11,7 @@ const use_theme_hooks_1 = require("../use-theme-hooks");
|
|
|
11
11
|
const use_theme_config_1 = require("../use-theme-config");
|
|
12
12
|
const contexts_1 = require("../../contexts");
|
|
13
13
|
const utils_1 = require("../../utils");
|
|
14
|
+
const constants_1 = require("../../constants");
|
|
14
15
|
function useSearchDialog() {
|
|
15
16
|
var _a;
|
|
16
17
|
var _b;
|
|
@@ -20,7 +21,7 @@ function useSearchDialog() {
|
|
|
20
21
|
const { useTelemetry } = (0, use_theme_hooks_1.useThemeHooks)();
|
|
21
22
|
const telemetry = useTelemetry();
|
|
22
23
|
const { refreshSearchSessionId } = (0, contexts_1.useSearchSession)();
|
|
23
|
-
const keyShortcuts = (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : [
|
|
24
|
+
const keyShortcuts = (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : [constants_1.DEFAULT_KEYBOARD_SHORTCUTS];
|
|
24
25
|
const hotKeys = keyShortcuts === null || keyShortcuts === void 0 ? void 0 : keyShortcuts.join(',');
|
|
25
26
|
(0, react_1.useEffect)(() => {
|
|
26
27
|
if (hotKeys) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0-next.0",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "4.1.8",
|
|
64
64
|
"vitest-when": "0.6.2",
|
|
65
65
|
"webpack": "5.105.2",
|
|
66
|
-
"@redocly/realm-asyncapi-sdk": "0.
|
|
66
|
+
"@redocly/realm-asyncapi-sdk": "0.14.0-next.0"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@tanstack/react-query": "5.62.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"openapi-sampler": "^1.7.2",
|
|
82
82
|
"react-calendar": "5.1.0",
|
|
83
83
|
"react-date-picker": "11.0.0",
|
|
84
|
-
"@redocly/config": "0.
|
|
84
|
+
"@redocly/config": "0.52.0"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"watch": "tsgo -p tsconfig.build.json && (concurrently \"tsgo -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useRef } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX } from 'react';
|
|
5
5
|
import type { MCPOption } from '@redocly/theme/core/types';
|
|
6
6
|
|
|
7
|
-
import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
|
|
8
7
|
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
|
|
9
8
|
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
10
9
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
@@ -12,21 +11,9 @@ import { CursorIcon } from '@redocly/theme/icons/CursorIcon/CursorIcon';
|
|
|
12
11
|
import { VSCodeIcon } from '@redocly/theme/icons/VSCodeIcon/VSCodeIcon';
|
|
13
12
|
import { CopyIcon } from '@redocly/theme/icons/CopyIcon/CopyIcon';
|
|
14
13
|
import { CheckmarkFilledIcon } from '@redocly/theme/icons/CheckmarkFilledIcon/CheckmarkFilledIcon';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
18
|
-
|
|
19
|
-
type TriggerButtonProps = {
|
|
20
|
-
text: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const TriggerButton = React.memo(({ text }: TriggerButtonProps): JSX.Element => {
|
|
24
|
-
return (
|
|
25
|
-
<StyledButton variant="secondary" icon={<ConnectIcon />}>
|
|
26
|
-
{text}
|
|
27
|
-
</StyledButton>
|
|
28
|
-
);
|
|
29
|
-
});
|
|
14
|
+
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
|
|
15
|
+
import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
|
|
16
|
+
import { useConnectMCPButton, useOutsideClick, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
30
17
|
|
|
31
18
|
type MenuOption = {
|
|
32
19
|
key: MCPOption;
|
|
@@ -74,57 +61,93 @@ export function ConnectMCPButton({
|
|
|
74
61
|
placement = 'bottom',
|
|
75
62
|
alignment = 'end',
|
|
76
63
|
options = ['cursor', 'vscode', 'copy'],
|
|
77
|
-
}: ConnectMCPButtonProps): JSX.Element {
|
|
64
|
+
}: ConnectMCPButtonProps): JSX.Element | null {
|
|
78
65
|
const { useTranslate } = useThemeHooks();
|
|
79
66
|
const { translate } = useTranslate();
|
|
67
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
68
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
80
69
|
|
|
81
|
-
const { isCopied,
|
|
82
|
-
options,
|
|
83
|
-
});
|
|
70
|
+
const { isCopied, visibleOptions, handleAction } = useConnectMCPButton({ options });
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
useOutsideClick(wrapperRef, () => setIsOpen(false));
|
|
73
|
+
|
|
74
|
+
const menuOptions = MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key));
|
|
75
|
+
const primaryOption = menuOptions[0];
|
|
76
|
+
const dropdownOptions = menuOptions.slice(1);
|
|
77
|
+
|
|
78
|
+
if (!primaryOption) return null;
|
|
79
|
+
|
|
80
|
+
const PrimaryIcon = primaryOption.icon;
|
|
81
|
+
// When copy is the primary action there is no dropdown item to show the copied state, so the
|
|
82
|
+
// primary button itself must reflect it.
|
|
83
|
+
const showPrimaryCopied = primaryOption.key === 'copy' && isCopied;
|
|
84
|
+
|
|
85
|
+
const handleMenuAction = (key: MCPOption) => {
|
|
86
|
+
handleAction(key);
|
|
87
|
+
setIsOpen(false);
|
|
88
|
+
};
|
|
89
89
|
|
|
90
90
|
return (
|
|
91
|
-
<ConnectMCPButtonWrapper data-component-name="Buttons/ConnectMCPButton">
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
91
|
+
<ConnectMCPButtonWrapper ref={wrapperRef} data-component-name="Buttons/ConnectMCPButton">
|
|
92
|
+
<SplitButtonGroup>
|
|
93
|
+
<PrimaryButton
|
|
94
|
+
variant="secondary"
|
|
95
|
+
icon={
|
|
96
|
+
showPrimaryCopied ? (
|
|
97
|
+
<CheckmarkFilledIcon color="var(--color-success-base)" />
|
|
98
|
+
) : (
|
|
99
|
+
<PrimaryIcon />
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
onClick={() => handleAction(primaryOption.key)}
|
|
103
|
+
>
|
|
104
|
+
{translate(primaryOption.titleTranslationKey, primaryOption.titleDefault)}
|
|
105
|
+
</PrimaryButton>
|
|
106
|
+
{dropdownOptions.length > 0 && (
|
|
107
|
+
<ChevronButton
|
|
108
|
+
variant="secondary"
|
|
109
|
+
aria-label={translate('page.actions.connectMcp.moreOptions', 'More options')}
|
|
110
|
+
aria-expanded={isOpen}
|
|
111
|
+
aria-haspopup="menu"
|
|
112
|
+
onClick={() => setIsOpen((prev) => !prev)}
|
|
113
|
+
>
|
|
114
|
+
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
|
115
|
+
</ChevronButton>
|
|
116
|
+
)}
|
|
117
|
+
</SplitButtonGroup>
|
|
118
|
+
|
|
119
|
+
{isOpen && dropdownOptions.length > 0 && (
|
|
120
|
+
<DropdownWrapper $placement={placement} $alignment={alignment}>
|
|
121
|
+
<DropdownMenu>
|
|
122
|
+
{dropdownOptions.map((option) => {
|
|
123
|
+
const Icon = option.icon;
|
|
124
|
+
const showCheckmark = option.key === 'copy' && isCopied;
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<DropdownMenuItem key={option.key} onAction={() => handleMenuAction(option.key)}>
|
|
128
|
+
<MenuItemContent>
|
|
129
|
+
<MenuItemIcon>
|
|
130
|
+
{showCheckmark ? (
|
|
131
|
+
<CheckmarkFilledIcon color="var(--color-success-base)" />
|
|
132
|
+
) : (
|
|
133
|
+
<Icon />
|
|
134
|
+
)}
|
|
135
|
+
</MenuItemIcon>
|
|
136
|
+
<MenuItemText>
|
|
137
|
+
<MenuItemTitle>
|
|
138
|
+
{translate(option.titleTranslationKey, option.titleDefault)}
|
|
139
|
+
</MenuItemTitle>
|
|
140
|
+
<MenuItemDescription>
|
|
141
|
+
{translate(option.descriptionTranslationKey, option.descriptionDefault)}
|
|
142
|
+
</MenuItemDescription>
|
|
143
|
+
</MenuItemText>
|
|
144
|
+
</MenuItemContent>
|
|
145
|
+
</DropdownMenuItem>
|
|
146
|
+
);
|
|
147
|
+
})}
|
|
148
|
+
</DropdownMenu>
|
|
149
|
+
</DropdownWrapper>
|
|
150
|
+
)}
|
|
128
151
|
</ConnectMCPButtonWrapper>
|
|
129
152
|
);
|
|
130
153
|
}
|
|
@@ -134,8 +157,32 @@ const ConnectMCPButtonWrapper = styled.div`
|
|
|
134
157
|
position: relative;
|
|
135
158
|
`;
|
|
136
159
|
|
|
137
|
-
const
|
|
160
|
+
const SplitButtonGroup = styled.div`
|
|
161
|
+
display: inline-flex;
|
|
162
|
+
align-items: stretch;
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const PrimaryButton = styled(Button)`
|
|
138
166
|
--button-gap: var(--connect-mcp-button-gap);
|
|
167
|
+
border-top-right-radius: 0;
|
|
168
|
+
border-bottom-right-radius: 0;
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
const ChevronButton = styled(Button)`
|
|
172
|
+
border-top-left-radius: 0;
|
|
173
|
+
border-bottom-left-radius: 0;
|
|
174
|
+
border-left: none;
|
|
175
|
+
padding-inline: var(--connect-mcp-button-chevron-padding-inline);
|
|
176
|
+
`;
|
|
177
|
+
|
|
178
|
+
const DropdownWrapper = styled.div<{ $placement?: string; $alignment?: string }>`
|
|
179
|
+
position: absolute;
|
|
180
|
+
top: ${({ $placement }) => ($placement === 'top' ? 'auto' : '100%')};
|
|
181
|
+
bottom: ${({ $placement }) => ($placement === 'top' ? '100%' : 'auto')};
|
|
182
|
+
left: ${({ $alignment }) => ($alignment === 'start' ? '0' : 'auto')};
|
|
183
|
+
right: ${({ $alignment }) => ($alignment === 'end' ? '0' : 'auto')};
|
|
184
|
+
padding-top: var(--dropdown-menu-padding-top);
|
|
185
|
+
z-index: var(--dropdown-menu-z-index);
|
|
139
186
|
`;
|
|
140
187
|
|
|
141
188
|
const MenuItemContent = styled.div`
|
|
@@ -58,6 +58,9 @@ export const connectMCPButton = css`
|
|
|
58
58
|
/* Button gap */
|
|
59
59
|
--connect-mcp-button-gap: var(--spacing-xs);
|
|
60
60
|
|
|
61
|
+
/* Chevron button */
|
|
62
|
+
--connect-mcp-button-chevron-padding-inline: var(--spacing-xs);
|
|
63
|
+
|
|
61
64
|
/* Menu item layout */
|
|
62
65
|
--connect-mcp-button-menu-item-gap: var(--spacing-sm);
|
|
63
66
|
--connect-mcp-button-menu-item-padding-block: var(--spacing-xxs);
|
|
@@ -16,7 +16,7 @@ export type ReasonsProps = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.Element | null {
|
|
19
|
-
const { label, component, items = [] } = settings;
|
|
19
|
+
const { label, component = 'checkbox', items = [] } = settings;
|
|
20
20
|
const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
|
|
21
21
|
const { useTranslate } = useThemeHooks();
|
|
22
22
|
const { translate } = useTranslate();
|
|
@@ -25,8 +25,6 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const input_type = component || 'checkbox';
|
|
29
|
-
|
|
30
28
|
const handleOptionChange = (position: number) => {
|
|
31
29
|
const updatedCheckedState =
|
|
32
30
|
component === 'checkbox'
|
|
@@ -53,7 +51,7 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
|
|
|
53
51
|
{items.map((reason, idx) => (
|
|
54
52
|
<OptionWrapper key={reason}>
|
|
55
53
|
<input
|
|
56
|
-
type={
|
|
54
|
+
type={component}
|
|
57
55
|
value={reason}
|
|
58
56
|
checked={checkedState[idx]}
|
|
59
57
|
name="reasons"
|
|
@@ -10,7 +10,7 @@ import { Link } from '@redocly/theme/components/Link/Link';
|
|
|
10
10
|
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
|
|
11
11
|
import { ChevronRightIcon } from '@redocly/theme/icons/ChevronRightIcon/ChevronRightIcon';
|
|
12
12
|
import { HttpTag } from '@redocly/theme/components/Tags/HttpTag';
|
|
13
|
-
import { MenuItemType } from '@redocly/theme/core/constants';
|
|
13
|
+
import { DEFAULT_LINE_POSITION, MenuItemType } from '@redocly/theme/core/constants';
|
|
14
14
|
import {
|
|
15
15
|
getBaseDataAttributes,
|
|
16
16
|
getMenuItemType,
|
|
@@ -178,7 +178,10 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
|
|
|
178
178
|
</MenuItemNestedWrapper>
|
|
179
179
|
) : null}
|
|
180
180
|
{item.separatorLine ? (
|
|
181
|
-
<MenuItemSeparatorLine
|
|
181
|
+
<MenuItemSeparatorLine
|
|
182
|
+
$depth={depth}
|
|
183
|
+
$linePosition={item.linePosition ?? DEFAULT_LINE_POSITION}
|
|
184
|
+
/>
|
|
182
185
|
) : null}
|
|
183
186
|
</MenuItemWrapper>
|
|
184
187
|
);
|
|
@@ -59,7 +59,7 @@ export function SearchAiDialog({
|
|
|
59
59
|
|
|
60
60
|
const conversationEndRef = React.useRef<HTMLDivElement>(null);
|
|
61
61
|
|
|
62
|
-
const suggestions = aiAssistant?.suggestions;
|
|
62
|
+
const suggestions = aiAssistant?.suggestions ?? [];
|
|
63
63
|
|
|
64
64
|
const placeholder = isGeneratingResponse
|
|
65
65
|
? translate('search.ai.generatingResponse', 'Generating response...')
|
|
@@ -42,7 +42,7 @@ export function SearchAiResponse(props: SearchAiResponseProps): JSX.Element {
|
|
|
42
42
|
|
|
43
43
|
let responseContainer = null;
|
|
44
44
|
|
|
45
|
-
const suggestions = aiAssistant?.suggestions;
|
|
45
|
+
const suggestions = aiAssistant?.suggestions ?? [];
|
|
46
46
|
|
|
47
47
|
const hasPendingOrReceivedResponse = response || isGeneratingResponse || error;
|
|
48
48
|
if (hasPendingOrReceivedResponse) {
|
|
@@ -7,6 +7,7 @@ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
|
7
7
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
8
8
|
import { SearchIcon } from '@redocly/theme/icons/SearchIcon/SearchIcon';
|
|
9
9
|
import { breakpoints, getUserAgent } from '@redocly/theme/core/utils';
|
|
10
|
+
import { DEFAULT_KEYBOARD_SHORTCUTS } from '@redocly/theme/core/constants';
|
|
10
11
|
|
|
11
12
|
export type SearchTriggerProps = {
|
|
12
13
|
onClick: () => void;
|
|
@@ -18,8 +19,7 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
|
|
|
18
19
|
const { useTranslate } = useThemeHooks();
|
|
19
20
|
const { translate } = useTranslate();
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
const keyShortcuts = themeSettings?.search?.shortcuts ?? [defaultKeyShortcut];
|
|
22
|
+
const keyShortcuts = themeSettings?.search?.shortcuts ?? [DEFAULT_KEYBOARD_SHORTCUTS];
|
|
23
23
|
let mainShortcutKey: string | null | undefined = null;
|
|
24
24
|
|
|
25
25
|
if (keyShortcuts) {
|
|
@@ -52,7 +52,7 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
|
|
|
52
52
|
>
|
|
53
53
|
<SearchIcon />
|
|
54
54
|
{translate('search.navbar.label', 'Search')}
|
|
55
|
-
{mainShortcutKey ===
|
|
55
|
+
{mainShortcutKey === DEFAULT_KEYBOARD_SHORTCUTS ? (
|
|
56
56
|
<span>{isMac ? '⌘K' : 'Ctrl+K'}</span>
|
|
57
57
|
) : (
|
|
58
58
|
<span>{mainShortcutKey}</span>
|
|
@@ -46,6 +46,8 @@ export const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
|
|
|
46
46
|
|
|
47
47
|
export const SEARCH_DEBOUNCE_TIME_MS = 300;
|
|
48
48
|
|
|
49
|
+
export const DEFAULT_KEYBOARD_SHORTCUTS = '⌘+K,CTRL+K';
|
|
50
|
+
|
|
49
51
|
export const TOOL_CALL_DISPLAY_TEXT: Partial<
|
|
50
52
|
Record<string, { inProgressText: string; completedText: string }>
|
|
51
53
|
> = {
|
|
@@ -6,6 +6,7 @@ import { useThemeHooks } from '../use-theme-hooks';
|
|
|
6
6
|
import { useThemeConfig } from '../use-theme-config';
|
|
7
7
|
import { useSearchSession } from '../../contexts';
|
|
8
8
|
import { getBaseDataAttributes } from '../../utils';
|
|
9
|
+
import { DEFAULT_KEYBOARD_SHORTCUTS } from '../../constants';
|
|
9
10
|
|
|
10
11
|
export function useSearchDialog() {
|
|
11
12
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -14,7 +15,7 @@ export function useSearchDialog() {
|
|
|
14
15
|
const { useTelemetry } = useThemeHooks();
|
|
15
16
|
const telemetry = useTelemetry();
|
|
16
17
|
const { refreshSearchSessionId } = useSearchSession();
|
|
17
|
-
const keyShortcuts = themeSettings?.search?.shortcuts ?? [
|
|
18
|
+
const keyShortcuts = themeSettings?.search?.shortcuts ?? [DEFAULT_KEYBOARD_SHORTCUTS];
|
|
18
19
|
const hotKeys = keyShortcuts?.join(',');
|
|
19
20
|
|
|
20
21
|
useEffect(() => {
|