@redocly/theme 0.69.0-next.10 → 0.69.0-next.11
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/MCP/McpConnectionTabs.js +21 -11
- package/lib/core/contexts/AccordionGroupContext.d.ts +1 -0
- package/lib/core/contexts/AccordionGroupContext.js +6 -0
- package/lib/core/contexts/index.d.ts +1 -0
- package/lib/core/contexts/index.js +1 -0
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +1 -0
- package/lib/core/hooks/use-active-heading.js +4 -0
- package/lib/core/hooks/use-connect-mcp-button.js +7 -2
- package/lib/core/hooks/use-tabs.js +4 -2
- package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
- package/lib/core/hooks/use-telemetry-fallback.js +2 -0
- package/lib/markdoc/components/Accordion/Accordion.js +6 -6
- package/lib/markdoc/components/Accordion/AccordionGroup.d.ts +2 -1
- package/lib/markdoc/components/Accordion/AccordionGroup.js +39 -3
- package/lib/markdoc/tags/accordion-group.js +3 -0
- package/package.json +2 -2
- package/src/components/MCP/McpConnectionTabs.tsx +32 -10
- package/src/core/contexts/AccordionGroupContext.tsx +3 -0
- package/src/core/contexts/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +1 -0
- package/src/core/hooks/use-active-heading.ts +4 -0
- package/src/core/hooks/use-connect-mcp-button.ts +8 -2
- package/src/core/hooks/use-tabs.ts +4 -2
- package/src/core/hooks/use-telemetry-fallback.ts +2 -0
- package/src/markdoc/components/Accordion/Accordion.tsx +8 -2
- package/src/markdoc/components/Accordion/AccordionGroup.tsx +9 -2
- package/src/markdoc/tags/accordion-group.ts +3 -0
|
@@ -62,11 +62,10 @@ const TAB_LABELS = {
|
|
|
62
62
|
},
|
|
63
63
|
json: { translationKey: 'page.mcpNotAllowed.tabs.json', defaultLabel: 'JSON' },
|
|
64
64
|
};
|
|
65
|
-
// The report control is docs feedback, not useful on the connection page.
|
|
66
|
-
const CODE_BLOCK_HEADER = { controls: { copy: {}, report: { hidden: true } } };
|
|
67
65
|
function McpConnectionTabs({ options = DEFAULT_TAB_OPTIONS, className, }) {
|
|
68
|
-
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
66
|
+
const { useTranslate, useTelemetry } = (0, hooks_1.useThemeHooks)();
|
|
69
67
|
const { translate } = useTranslate();
|
|
68
|
+
const telemetry = useTelemetry();
|
|
70
69
|
const { serverName, serverUrl, isMcpDisabled } = (0, hooks_1.useMCPConfig)();
|
|
71
70
|
// The SSR markup of error pages is cached with the build-time origin, so the
|
|
72
71
|
// tabs remount after hydration to pick up the real browser origin.
|
|
@@ -76,20 +75,31 @@ function McpConnectionTabs({ options = DEFAULT_TAB_OPTIONS, className, }) {
|
|
|
76
75
|
return null;
|
|
77
76
|
}
|
|
78
77
|
const connection = { serverName, url: serverUrl };
|
|
78
|
+
const snippetHeader = (client, snippet) => ({
|
|
79
|
+
controls: {
|
|
80
|
+
copy: {
|
|
81
|
+
onClick: () => telemetry.sendMcpConnectionSnippetCopiedMessage([
|
|
82
|
+
Object.assign(Object.assign({}, (0, utils_1.getBaseDataAttributes)('mcpConnectionSnippet', 'button')), { client, snippet }),
|
|
83
|
+
]),
|
|
84
|
+
},
|
|
85
|
+
// The report control is docs feedback, not useful on the connection page.
|
|
86
|
+
report: { hidden: true },
|
|
87
|
+
},
|
|
88
|
+
});
|
|
79
89
|
return (react_1.default.createElement(McpConnectionTabsWrapper, { key: hydrated ? 'client' : 'ssr', "data-component-name": "MCP/McpConnectionTabs", "data-testid": "mcp-connection-tabs", className: className },
|
|
80
90
|
react_1.default.createElement(Tabs_1.Tabs, { size: Tabs_1.TabsSize.SMALL }, options.map((option) => {
|
|
81
91
|
const { translationKey, defaultLabel, icon: TabIcon } = TAB_LABELS[option];
|
|
82
|
-
return (react_1.default.createElement(TabItemFragment_1.TabItemFragment, { key: option, label: translate(translationKey, defaultLabel), icon: TabIcon ? react_1.default.createElement(TabIcon, null) : undefined }, renderTabContent(option, connection, translate)));
|
|
92
|
+
return (react_1.default.createElement(TabItemFragment_1.TabItemFragment, { key: option, label: translate(translationKey, defaultLabel), icon: TabIcon ? react_1.default.createElement(TabIcon, null) : undefined }, renderTabContent(option, connection, translate, snippetHeader)));
|
|
83
93
|
}))));
|
|
84
94
|
}
|
|
85
|
-
function renderTabContent(option, connection, translate) {
|
|
95
|
+
function renderTabContent(option, connection, translate, snippetHeader) {
|
|
86
96
|
const terminalHint = (react_1.default.createElement(TabHint, null, translate('page.mcpNotAllowed.snippets.terminal.hint', 'Run this command in your terminal:')));
|
|
87
|
-
const urlBlock = react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "text", source: connection.url, header:
|
|
97
|
+
const urlBlock = (react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "text", source: connection.url, header: snippetHeader(option, 'chatgpt-app') }));
|
|
88
98
|
const assistantPromptClient = {
|
|
89
99
|
headline: translate('page.mcpNotAllowed.snippets.prompt.headline', 'Prompt'),
|
|
90
100
|
content: (react_1.default.createElement(react_1.default.Fragment, null,
|
|
91
101
|
react_1.default.createElement(TabHint, null, translate('page.mcpNotAllowed.snippets.prompt.hint', 'Or paste this prompt into the chat to have it add the server for you:')),
|
|
92
|
-
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "text", source: (0, utils_1.generateMcpInstallPrompt)(connection), header:
|
|
102
|
+
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "text", source: (0, utils_1.generateMcpInstallPrompt)(connection), header: snippetHeader(option, 'prompt'), wrapContents: true }))),
|
|
93
103
|
};
|
|
94
104
|
switch (option) {
|
|
95
105
|
case 'claude':
|
|
@@ -98,13 +108,13 @@ function renderTabContent(option, connection, translate) {
|
|
|
98
108
|
headline: translate('page.mcpNotAllowed.snippets.claudeCode.headline', 'Claude Code'),
|
|
99
109
|
content: (react_1.default.createElement(react_1.default.Fragment, null,
|
|
100
110
|
terminalHint,
|
|
101
|
-
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "shell", source: (0, utils_1.generateClaudeCodeCommand)(connection), header:
|
|
111
|
+
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "shell", source: (0, utils_1.generateClaudeCodeCommand)(connection), header: snippetHeader(option, 'claude-code'), wrapContents: true }))),
|
|
102
112
|
},
|
|
103
113
|
{
|
|
104
114
|
headline: translate('page.mcpNotAllowed.snippets.claudeDesktop.headline', 'Claude Desktop'),
|
|
105
115
|
content: (react_1.default.createElement(react_1.default.Fragment, null,
|
|
106
116
|
react_1.default.createElement(TabHint, null, translate('page.mcpNotAllowed.snippets.claudeDesktop.instruction', 'In Claude Desktop, open Settings → Developer → Edit Config, add this to the configuration file, and restart Claude Desktop:')),
|
|
107
|
-
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "json", source: (0, utils_1.generateClaudeDesktopConfig)(connection), header:
|
|
117
|
+
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "json", source: (0, utils_1.generateClaudeDesktopConfig)(connection), header: snippetHeader(option, 'claude-desktop') }))),
|
|
108
118
|
},
|
|
109
119
|
]);
|
|
110
120
|
case 'chatgpt':
|
|
@@ -113,7 +123,7 @@ function renderTabContent(option, connection, translate) {
|
|
|
113
123
|
headline: translate('page.mcpNotAllowed.snippets.codexCli.headline', 'Codex CLI'),
|
|
114
124
|
content: (react_1.default.createElement(react_1.default.Fragment, null,
|
|
115
125
|
terminalHint,
|
|
116
|
-
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "shell", source: (0, utils_1.generateCodexCommand)(connection), header:
|
|
126
|
+
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "shell", source: (0, utils_1.generateCodexCommand)(connection), header: snippetHeader(option, 'codex'), wrapContents: true }))),
|
|
117
127
|
},
|
|
118
128
|
{
|
|
119
129
|
headline: translate('page.mcpNotAllowed.snippets.chatgptApp.headline', 'ChatGPT desktop app'),
|
|
@@ -126,7 +136,7 @@ function renderTabContent(option, connection, translate) {
|
|
|
126
136
|
case 'json':
|
|
127
137
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
128
138
|
react_1.default.createElement(TabHint, null, translate('page.mcpNotAllowed.snippets.json.hint', 'Add this server entry to your MCP client configuration file:')),
|
|
129
|
-
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "json", source: JSON.stringify({ [connection.serverName]: { url: connection.url, description: 'MCP Server' } }, null, 2), header:
|
|
139
|
+
react_1.default.createElement(CodeBlock_1.CodeBlock, { lang: "json", source: JSON.stringify({ [connection.serverName]: { url: connection.url, description: 'MCP Server' } }, null, 2), header: snippetHeader(option, 'json') })));
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
142
|
function renderClientList(clients) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AccordionGroupContext: import("react").Context<string | null>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccordionGroupContext = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
exports.AccordionGroupContext = (0, react_1.createContext)(null);
|
|
6
|
+
//# sourceMappingURL=AccordionGroupContext.js.map
|
|
@@ -3,6 +3,7 @@ export * from './MarkdownLinkContext';
|
|
|
3
3
|
export * from './PageActionsContext';
|
|
4
4
|
export * from './CodeWalkthrough/CodeWalkthroughControlsContext';
|
|
5
5
|
export * from './CodeWalkthrough/CodeWalkthroughStepsContext';
|
|
6
|
+
export * from './AccordionGroupContext';
|
|
6
7
|
export * from './CodeSnippetContext';
|
|
7
8
|
export * from './SearchContext';
|
|
8
9
|
export * from './Toast/ToastContext';
|
|
@@ -19,6 +19,7 @@ __exportStar(require("./MarkdownLinkContext"), exports);
|
|
|
19
19
|
__exportStar(require("./PageActionsContext"), exports);
|
|
20
20
|
__exportStar(require("./CodeWalkthrough/CodeWalkthroughControlsContext"), exports);
|
|
21
21
|
__exportStar(require("./CodeWalkthrough/CodeWalkthroughStepsContext"), exports);
|
|
22
|
+
__exportStar(require("./AccordionGroupContext"), exports);
|
|
22
23
|
__exportStar(require("./CodeSnippetContext"), exports);
|
|
23
24
|
__exportStar(require("./SearchContext"), exports);
|
|
24
25
|
__exportStar(require("./Toast/ToastContext"), exports);
|
|
@@ -58,6 +58,7 @@ function useCodeWalkthroughSteps({ steps, enableDeepLink, root, }) {
|
|
|
58
58
|
startOffset: markersMinTopOffset < endOffset ? startOffset : 0,
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- marker geometry is derived from post-layout measurements (offsetTop, clientHeight, window.innerHeight)
|
|
61
62
|
setMarkers(markers.reduce((acc, marker, index) => {
|
|
62
63
|
var _a;
|
|
63
64
|
const step = visibleSteps[index];
|
|
@@ -205,19 +205,23 @@ function useActiveHeading(contentElement, displayedHeadings) {
|
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
207
|
const headers = findHeaders(contentElement);
|
|
208
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- headings and the initial active heading come from DOM queries and scroll measurement, available only after layout
|
|
208
209
|
setHeadingElements(headers);
|
|
209
210
|
if (headers.length > 0 && !heading) {
|
|
210
211
|
if (isTopOfPage()) {
|
|
212
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
211
213
|
setHeading(headers[0].id);
|
|
212
214
|
}
|
|
213
215
|
else {
|
|
214
216
|
const totalHeight = scrollY + window.innerHeight;
|
|
215
217
|
if (totalHeight >= document.body.scrollHeight - 10) {
|
|
218
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
216
219
|
setHeading(((_a = headers[headers.length - 1]) === null || _a === void 0 ? void 0 : _a.id) || undefined);
|
|
217
220
|
}
|
|
218
221
|
else {
|
|
219
222
|
const triggerOffset = getTriggerOffset();
|
|
220
223
|
const activeHeader = findActiveHeaderByVirtualPosition(headers, scrollY, triggerOffset);
|
|
224
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
221
225
|
setHeading(activeHeader.id);
|
|
222
226
|
}
|
|
223
227
|
}
|
|
@@ -11,15 +11,20 @@ const use_mcp_config_1 = require("./use-mcp-config");
|
|
|
11
11
|
const clipboard_service_1 = require("../utils/clipboard-service");
|
|
12
12
|
const mcp_1 = require("../constants/mcp");
|
|
13
13
|
const mcp_2 = require("../utils/mcp");
|
|
14
|
+
const get_base_data_attributes_1 = require("../utils/telemetry/get-base-data-attributes");
|
|
14
15
|
const COPIED_RESET_TIMEOUT = 1000;
|
|
15
16
|
function useConnectMCPButton({ options = mcp_1.DEFAULT_MCP_OPTIONS, } = {}) {
|
|
16
|
-
const { useTranslate } = (0, use_theme_hooks_1.useThemeHooks)();
|
|
17
|
+
const { useTranslate, useTelemetry } = (0, use_theme_hooks_1.useThemeHooks)();
|
|
17
18
|
const { translate } = useTranslate();
|
|
19
|
+
const telemetry = useTelemetry();
|
|
18
20
|
const { serverName, serverUrl, cursorUrl, vscodeUrl } = (0, use_mcp_config_1.useMCPConfig)();
|
|
19
21
|
const [copiedOption, setCopiedOption] = (0, react_1.useState)(null);
|
|
20
22
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
21
23
|
const resetCopied = (0, react_1.useCallback)((0, lodash_debounce_1.default)(() => setCopiedOption(null), COPIED_RESET_TIMEOUT), []);
|
|
22
24
|
const handleAction = (0, react_1.useCallback)((action) => {
|
|
25
|
+
telemetry.sendConnectMcpClickedMessage([
|
|
26
|
+
Object.assign(Object.assign({}, (0, get_base_data_attributes_1.getBaseDataAttributes)('connectMcp', 'button')), { option: action }),
|
|
27
|
+
]);
|
|
23
28
|
const connection = { serverName, url: serverUrl };
|
|
24
29
|
const copyTextMap = {
|
|
25
30
|
copy: JSON.stringify({ [serverName]: { url: serverUrl, description: 'MCP Server' } }, null, 2),
|
|
@@ -38,7 +43,7 @@ function useConnectMCPButton({ options = mcp_1.DEFAULT_MCP_OPTIONS, } = {}) {
|
|
|
38
43
|
vscode: vscodeUrl,
|
|
39
44
|
};
|
|
40
45
|
window.open(urlMap[action], '_blank');
|
|
41
|
-
}, [cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied]);
|
|
46
|
+
}, [cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied, telemetry]);
|
|
42
47
|
const triggerButtonText = (0, react_1.useMemo)(() => translate('page.actions.connectMcp', 'Connect MCP'), [translate]);
|
|
43
48
|
const visibleOptions = (0, react_1.useMemo)(() => options.filter(Boolean), [options]);
|
|
44
49
|
return {
|
|
@@ -220,12 +220,14 @@ const useActiveTab = ({ initialTab, tabsId }) => {
|
|
|
220
220
|
const [activeTab, setActiveTab] = (0, react_1.useState)(undefined);
|
|
221
221
|
const resolvedActiveTab = activeTab !== null && activeTab !== void 0 ? activeTab : initialTab;
|
|
222
222
|
const prevActiveTabRef = (0, react_1.useRef)(resolvedActiveTab);
|
|
223
|
+
const isTabResolvedFromHashRef = (0, react_1.useRef)(false);
|
|
223
224
|
(0, react_1.useEffect)(() => {
|
|
224
|
-
if (
|
|
225
|
+
if (isTabResolvedFromHashRef.current) {
|
|
225
226
|
return;
|
|
226
227
|
}
|
|
228
|
+
isTabResolvedFromHashRef.current = true;
|
|
227
229
|
setActiveTab(getInitialTab({ initialTab, hash: location.hash, tabsId }));
|
|
228
|
-
}, [
|
|
230
|
+
}, [initialTab, location.hash, tabsId]);
|
|
229
231
|
(0, react_1.useEffect)(() => {
|
|
230
232
|
const hasActiveTabChanged = prevActiveTabRef.current !== resolvedActiveTab;
|
|
231
233
|
if (!tabsId || !hasActiveTabChanged) {
|
|
@@ -34,6 +34,8 @@ export declare const useTelemetryFallback: () => {
|
|
|
34
34
|
sendProductPickerChangedMessage: () => void;
|
|
35
35
|
sendLogoClickedMessage: () => void;
|
|
36
36
|
sendCodeSnippetReportedMessage: () => void;
|
|
37
|
+
sendConnectMcpClickedMessage: () => void;
|
|
38
|
+
sendMcpConnectionSnippetCopiedMessage: () => void;
|
|
37
39
|
sendMobileMenuClosedMessage: () => void;
|
|
38
40
|
sendMobileMenuOpenedMessage: () => void;
|
|
39
41
|
sendPageViewedMessage: () => void;
|
|
@@ -39,6 +39,8 @@ const useTelemetryFallback = () => ({
|
|
|
39
39
|
sendProductPickerChangedMessage: () => { },
|
|
40
40
|
sendLogoClickedMessage: () => { },
|
|
41
41
|
sendCodeSnippetReportedMessage: () => { },
|
|
42
|
+
sendConnectMcpClickedMessage: () => { },
|
|
43
|
+
sendMcpConnectionSnippetCopiedMessage: () => { },
|
|
42
44
|
sendMobileMenuClosedMessage: () => { },
|
|
43
45
|
sendMobileMenuOpenedMessage: () => { },
|
|
44
46
|
sendPageViewedMessage: () => { },
|
|
@@ -32,21 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.Accordion = Accordion;
|
|
40
|
-
const react_1 =
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
41
38
|
const styled_components_1 = __importStar(require("styled-components"));
|
|
42
39
|
const ChevronRightIcon_1 = require("../../../icons/ChevronRightIcon/ChevronRightIcon");
|
|
43
40
|
const constants_1 = require("../../../core/constants");
|
|
41
|
+
const contexts_1 = require("../../../core/contexts");
|
|
44
42
|
function Accordion({ title, expanded = false, className, children, 'data-source': dataSource, 'data-hash': dataHash, }) {
|
|
45
|
-
|
|
43
|
+
const groupName = (0, react_1.useContext)(contexts_1.AccordionGroupContext);
|
|
44
|
+
return (react_1.default.createElement(AccordionWrapper, { className: className, open: expanded, name: groupName !== null && groupName !== void 0 ? groupName : undefined, "data-component-name": constants_1.ACCORDION_COMPONENT_NAME, "data-source": dataSource, "data-hash": dataHash },
|
|
46
45
|
react_1.default.createElement(AccordionSummary, null,
|
|
47
46
|
react_1.default.createElement(ChevronRightIcon_1.ChevronRightIcon, { color: "--md-accordion-chevron-icon-color", size: "var(--md-accordion-chevron-icon-size)" }),
|
|
48
47
|
title),
|
|
49
|
-
react_1.default.createElement(AccordionBody, null,
|
|
48
|
+
react_1.default.createElement(AccordionBody, null,
|
|
49
|
+
react_1.default.createElement(contexts_1.AccordionGroupContext.Provider, { value: null }, children))));
|
|
50
50
|
}
|
|
51
51
|
const AccordionWrapper = styled_components_1.default.details `
|
|
52
52
|
margin: var(--md-accordion-margin);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX, PropsWithChildren } from 'react';
|
|
2
2
|
export type AccordionGroupProps = {
|
|
3
|
+
singleExpanded?: boolean;
|
|
3
4
|
className?: string;
|
|
4
5
|
};
|
|
5
|
-
export declare function AccordionGroup({ className, children, }: PropsWithChildren<AccordionGroupProps>): JSX.Element;
|
|
6
|
+
export declare function AccordionGroup({ singleExpanded, className, children, }: PropsWithChildren<AccordionGroupProps>): JSX.Element;
|
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.AccordionGroup = AccordionGroup;
|
|
7
|
-
const react_1 =
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
8
41
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
42
|
const constants_1 = require("../../../core/constants");
|
|
10
|
-
|
|
11
|
-
|
|
43
|
+
const contexts_1 = require("../../../core/contexts");
|
|
44
|
+
function AccordionGroup({ singleExpanded = false, className, children, }) {
|
|
45
|
+
const groupName = (0, react_1.useId)();
|
|
46
|
+
return (react_1.default.createElement(AccordionGroupWrapper, { className: className, "data-component-name": constants_1.ACCORDION_GROUP_COMPONENT_NAME },
|
|
47
|
+
react_1.default.createElement(contexts_1.AccordionGroupContext.Provider, { value: singleExpanded ? groupName : null }, children)));
|
|
12
48
|
}
|
|
13
49
|
const AccordionGroupWrapper = styled_components_1.default.div `
|
|
14
50
|
margin: var(--md-accordion-group-margin);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.69.0-next.
|
|
3
|
+
"version": "0.69.0-next.11",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@babel/core": "7.29.7",
|
|
44
44
|
"@markdoc/markdoc": "0.5.2",
|
|
45
|
-
"@redocly/realm-asyncapi-sdk": "0.15.0-next.
|
|
45
|
+
"@redocly/realm-asyncapi-sdk": "0.15.0-next.4",
|
|
46
46
|
"@shikijs/types": "3.21.0",
|
|
47
47
|
"@testing-library/react": "16.3.0",
|
|
48
48
|
"@testing-library/user-event": "14.6.1",
|
|
@@ -3,6 +3,8 @@ import styled from 'styled-components';
|
|
|
3
3
|
|
|
4
4
|
import type { JSX, ReactNode } from 'react';
|
|
5
5
|
import type { TFunction } from '@redocly/theme/core/types';
|
|
6
|
+
import type { AsyncApiRealmUI } from '@redocly/realm-asyncapi-sdk';
|
|
7
|
+
import type { CodeBlockControlsProps } from '@redocly/theme/components/CodeBlock/CodeBlockControls';
|
|
6
8
|
|
|
7
9
|
import { useMCPConfig, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
8
10
|
import {
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
generateClaudeDesktopConfig,
|
|
11
13
|
generateCodexCommand,
|
|
12
14
|
generateMcpInstallPrompt,
|
|
15
|
+
getBaseDataAttributes,
|
|
13
16
|
} from '@redocly/theme/core/utils';
|
|
14
17
|
import { CodeBlock } from '@redocly/theme/components/CodeBlock/CodeBlock';
|
|
15
18
|
import { ClaudeIcon } from '@redocly/theme/icons/ClaudeIcon/ClaudeIcon';
|
|
@@ -47,15 +50,18 @@ const TAB_LABELS: Record<
|
|
|
47
50
|
|
|
48
51
|
type TabClient = { headline: string; content: ReactNode };
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
type SnippetId =
|
|
54
|
+
AsyncApiRealmUI.EventPayload<'com.redocly.mcpConnectionSnippet.copied'>[0]['snippet'];
|
|
55
|
+
|
|
56
|
+
type SnippetHeader = (client: McpConnectionTab, snippet: SnippetId) => CodeBlockControlsProps;
|
|
52
57
|
|
|
53
58
|
export function McpConnectionTabs({
|
|
54
59
|
options = DEFAULT_TAB_OPTIONS,
|
|
55
60
|
className,
|
|
56
61
|
}: McpConnectionTabsProps): JSX.Element | null {
|
|
57
|
-
const { useTranslate } = useThemeHooks();
|
|
62
|
+
const { useTranslate, useTelemetry } = useThemeHooks();
|
|
58
63
|
const { translate } = useTranslate();
|
|
64
|
+
const telemetry = useTelemetry();
|
|
59
65
|
const { serverName, serverUrl, isMcpDisabled } = useMCPConfig();
|
|
60
66
|
// The SSR markup of error pages is cached with the build-time origin, so the
|
|
61
67
|
// tabs remount after hydration to pick up the real browser origin.
|
|
@@ -69,6 +75,19 @@ export function McpConnectionTabs({
|
|
|
69
75
|
|
|
70
76
|
const connection = { serverName, url: serverUrl };
|
|
71
77
|
|
|
78
|
+
const snippetHeader: SnippetHeader = (client, snippet) => ({
|
|
79
|
+
controls: {
|
|
80
|
+
copy: {
|
|
81
|
+
onClick: () =>
|
|
82
|
+
telemetry.sendMcpConnectionSnippetCopiedMessage([
|
|
83
|
+
{ ...getBaseDataAttributes('mcpConnectionSnippet', 'button'), client, snippet },
|
|
84
|
+
]),
|
|
85
|
+
},
|
|
86
|
+
// The report control is docs feedback, not useful on the connection page.
|
|
87
|
+
report: { hidden: true },
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
72
91
|
return (
|
|
73
92
|
<McpConnectionTabsWrapper
|
|
74
93
|
key={hydrated ? 'client' : 'ssr'}
|
|
@@ -86,7 +105,7 @@ export function McpConnectionTabs({
|
|
|
86
105
|
label={translate(translationKey, defaultLabel)}
|
|
87
106
|
icon={TabIcon ? <TabIcon /> : undefined}
|
|
88
107
|
>
|
|
89
|
-
{renderTabContent(option, connection, translate)}
|
|
108
|
+
{renderTabContent(option, connection, translate, snippetHeader)}
|
|
90
109
|
</TabItemFragment>
|
|
91
110
|
);
|
|
92
111
|
})}
|
|
@@ -99,6 +118,7 @@ function renderTabContent(
|
|
|
99
118
|
option: McpConnectionTab,
|
|
100
119
|
connection: { serverName: string; url: string },
|
|
101
120
|
translate: TFunction,
|
|
121
|
+
snippetHeader: SnippetHeader,
|
|
102
122
|
): ReactNode {
|
|
103
123
|
const terminalHint = (
|
|
104
124
|
<TabHint>
|
|
@@ -106,7 +126,9 @@ function renderTabContent(
|
|
|
106
126
|
</TabHint>
|
|
107
127
|
);
|
|
108
128
|
|
|
109
|
-
const urlBlock =
|
|
129
|
+
const urlBlock = (
|
|
130
|
+
<CodeBlock lang="text" source={connection.url} header={snippetHeader(option, 'chatgpt-app')} />
|
|
131
|
+
);
|
|
110
132
|
|
|
111
133
|
const assistantPromptClient: TabClient = {
|
|
112
134
|
headline: translate('page.mcpNotAllowed.snippets.prompt.headline', 'Prompt'),
|
|
@@ -121,7 +143,7 @@ function renderTabContent(
|
|
|
121
143
|
<CodeBlock
|
|
122
144
|
lang="text"
|
|
123
145
|
source={generateMcpInstallPrompt(connection)}
|
|
124
|
-
header={
|
|
146
|
+
header={snippetHeader(option, 'prompt')}
|
|
125
147
|
wrapContents
|
|
126
148
|
/>
|
|
127
149
|
</>
|
|
@@ -139,7 +161,7 @@ function renderTabContent(
|
|
|
139
161
|
<CodeBlock
|
|
140
162
|
lang="shell"
|
|
141
163
|
source={generateClaudeCodeCommand(connection)}
|
|
142
|
-
header={
|
|
164
|
+
header={snippetHeader(option, 'claude-code')}
|
|
143
165
|
wrapContents
|
|
144
166
|
/>
|
|
145
167
|
</>
|
|
@@ -161,7 +183,7 @@ function renderTabContent(
|
|
|
161
183
|
<CodeBlock
|
|
162
184
|
lang="json"
|
|
163
185
|
source={generateClaudeDesktopConfig(connection)}
|
|
164
|
-
header={
|
|
186
|
+
header={snippetHeader(option, 'claude-desktop')}
|
|
165
187
|
/>
|
|
166
188
|
</>
|
|
167
189
|
),
|
|
@@ -177,7 +199,7 @@ function renderTabContent(
|
|
|
177
199
|
<CodeBlock
|
|
178
200
|
lang="shell"
|
|
179
201
|
source={generateCodexCommand(connection)}
|
|
180
|
-
header={
|
|
202
|
+
header={snippetHeader(option, 'codex')}
|
|
181
203
|
wrapContents
|
|
182
204
|
/>
|
|
183
205
|
</>
|
|
@@ -218,7 +240,7 @@ function renderTabContent(
|
|
|
218
240
|
null,
|
|
219
241
|
2,
|
|
220
242
|
)}
|
|
221
|
-
header={
|
|
243
|
+
header={snippetHeader(option, 'json')}
|
|
222
244
|
/>
|
|
223
245
|
</>
|
|
224
246
|
);
|
|
@@ -3,6 +3,7 @@ export * from './MarkdownLinkContext';
|
|
|
3
3
|
export * from './PageActionsContext';
|
|
4
4
|
export * from './CodeWalkthrough/CodeWalkthroughControlsContext';
|
|
5
5
|
export * from './CodeWalkthrough/CodeWalkthroughStepsContext';
|
|
6
|
+
export * from './AccordionGroupContext';
|
|
6
7
|
export * from './CodeSnippetContext';
|
|
7
8
|
export * from './SearchContext';
|
|
8
9
|
export * from './Toast/ToastContext';
|
|
@@ -18,6 +18,8 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
18
18
|
useTelemetry: vi.fn(() => ({
|
|
19
19
|
send: vi.fn(),
|
|
20
20
|
sendCodeSnippetReportedMessage: vi.fn(),
|
|
21
|
+
sendConnectMcpClickedMessage: vi.fn(),
|
|
22
|
+
sendMcpConnectionSnippetCopiedMessage: vi.fn(),
|
|
21
23
|
sendPageActionsButtonClickedMessage: vi.fn(),
|
|
22
24
|
sendColorModeSwitchedMessage: vi.fn(),
|
|
23
25
|
})),
|
|
@@ -95,6 +95,7 @@ export function useCodeWalkthroughSteps({
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- marker geometry is derived from post-layout measurements (offsetTop, clientHeight, window.innerHeight)
|
|
98
99
|
setMarkers(
|
|
99
100
|
markers.reduce(
|
|
100
101
|
(acc, marker, index) => {
|
|
@@ -286,18 +286,22 @@ export function useActiveHeading(
|
|
|
286
286
|
return;
|
|
287
287
|
}
|
|
288
288
|
const headers = findHeaders(contentElement);
|
|
289
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- headings and the initial active heading come from DOM queries and scroll measurement, available only after layout
|
|
289
290
|
setHeadingElements(headers);
|
|
290
291
|
|
|
291
292
|
if (headers.length > 0 && !heading) {
|
|
292
293
|
if (isTopOfPage()) {
|
|
294
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
293
295
|
setHeading(headers[0].id);
|
|
294
296
|
} else {
|
|
295
297
|
const totalHeight = scrollY + window.innerHeight;
|
|
296
298
|
if (totalHeight >= document.body.scrollHeight - 10) {
|
|
299
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
297
300
|
setHeading(headers[headers.length - 1]?.id || undefined);
|
|
298
301
|
} else {
|
|
299
302
|
const triggerOffset = getTriggerOffset();
|
|
300
303
|
const activeHeader = findActiveHeaderByVirtualPosition(headers, scrollY, triggerOffset);
|
|
304
|
+
// oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
|
|
301
305
|
setHeading(activeHeader.id);
|
|
302
306
|
}
|
|
303
307
|
}
|
|
@@ -8,6 +8,7 @@ import { useMCPConfig } from './use-mcp-config';
|
|
|
8
8
|
import { ClipboardService } from '../utils/clipboard-service';
|
|
9
9
|
import { DEFAULT_MCP_OPTIONS } from '../constants/mcp';
|
|
10
10
|
import { generateClaudeCodeCommand, generateCodexCommand } from '../utils/mcp';
|
|
11
|
+
import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
|
|
11
12
|
|
|
12
13
|
export type McpButtonOptions = {
|
|
13
14
|
options?: MCPOption[];
|
|
@@ -27,8 +28,9 @@ const COPIED_RESET_TIMEOUT = 1000;
|
|
|
27
28
|
export function useConnectMCPButton({
|
|
28
29
|
options = DEFAULT_MCP_OPTIONS,
|
|
29
30
|
}: McpButtonOptions = {}): McpButtonSettings {
|
|
30
|
-
const { useTranslate } = useThemeHooks();
|
|
31
|
+
const { useTranslate, useTelemetry } = useThemeHooks();
|
|
31
32
|
const { translate } = useTranslate();
|
|
33
|
+
const telemetry = useTelemetry();
|
|
32
34
|
const { serverName, serverUrl, cursorUrl, vscodeUrl } = useMCPConfig();
|
|
33
35
|
const [copiedOption, setCopiedOption] = useState<MCPOption | null>(null);
|
|
34
36
|
|
|
@@ -40,6 +42,10 @@ export function useConnectMCPButton({
|
|
|
40
42
|
|
|
41
43
|
const handleAction = useCallback(
|
|
42
44
|
(action: MCPOption) => {
|
|
45
|
+
telemetry.sendConnectMcpClickedMessage([
|
|
46
|
+
{ ...getBaseDataAttributes('connectMcp', 'button'), option: action },
|
|
47
|
+
]);
|
|
48
|
+
|
|
43
49
|
const connection = { serverName, url: serverUrl };
|
|
44
50
|
const copyTextMap: Partial<Record<MCPOption, string>> = {
|
|
45
51
|
copy: JSON.stringify(
|
|
@@ -66,7 +72,7 @@ export function useConnectMCPButton({
|
|
|
66
72
|
|
|
67
73
|
window.open(urlMap[action], '_blank');
|
|
68
74
|
},
|
|
69
|
-
[cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied],
|
|
75
|
+
[cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied, telemetry],
|
|
70
76
|
);
|
|
71
77
|
|
|
72
78
|
const triggerButtonText = useMemo(
|
|
@@ -289,14 +289,16 @@ export const useActiveTab = ({ initialTab, tabsId }: UseActiveTabProps) => {
|
|
|
289
289
|
const [activeTab, setActiveTab] = useState<string | undefined>(undefined);
|
|
290
290
|
const resolvedActiveTab = activeTab ?? initialTab;
|
|
291
291
|
const prevActiveTabRef = useRef(resolvedActiveTab);
|
|
292
|
+
const isTabResolvedFromHashRef = useRef(false);
|
|
292
293
|
|
|
293
294
|
useEffect(() => {
|
|
294
|
-
if (
|
|
295
|
+
if (isTabResolvedFromHashRef.current) {
|
|
295
296
|
return;
|
|
296
297
|
}
|
|
298
|
+
isTabResolvedFromHashRef.current = true;
|
|
297
299
|
|
|
298
300
|
setActiveTab(getInitialTab({ initialTab, hash: location.hash, tabsId }));
|
|
299
|
-
}, [
|
|
301
|
+
}, [initialTab, location.hash, tabsId]);
|
|
300
302
|
|
|
301
303
|
useEffect(() => {
|
|
302
304
|
const hasActiveTabChanged = prevActiveTabRef.current !== resolvedActiveTab;
|
|
@@ -36,6 +36,8 @@ export const useTelemetryFallback = () => ({
|
|
|
36
36
|
sendProductPickerChangedMessage: () => {},
|
|
37
37
|
sendLogoClickedMessage: () => {},
|
|
38
38
|
sendCodeSnippetReportedMessage: () => {},
|
|
39
|
+
sendConnectMcpClickedMessage: () => {},
|
|
40
|
+
sendMcpConnectionSnippetCopiedMessage: () => {},
|
|
39
41
|
sendMobileMenuClosedMessage: () => {},
|
|
40
42
|
sendMobileMenuOpenedMessage: () => {},
|
|
41
43
|
sendPageViewedMessage: () => {},
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import styled, { keyframes } from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX, PropsWithChildren } from 'react';
|
|
5
5
|
|
|
6
6
|
import { ChevronRightIcon } from '@redocly/theme/icons/ChevronRightIcon/ChevronRightIcon';
|
|
7
7
|
import { ACCORDION_COMPONENT_NAME } from '@redocly/theme/core/constants';
|
|
8
|
+
import { AccordionGroupContext } from '@redocly/theme/core/contexts';
|
|
8
9
|
|
|
9
10
|
export type AccordionProps = {
|
|
10
11
|
title: string;
|
|
@@ -22,10 +23,13 @@ export function Accordion({
|
|
|
22
23
|
'data-source': dataSource,
|
|
23
24
|
'data-hash': dataHash,
|
|
24
25
|
}: PropsWithChildren<AccordionProps>): JSX.Element {
|
|
26
|
+
const groupName = useContext(AccordionGroupContext);
|
|
27
|
+
|
|
25
28
|
return (
|
|
26
29
|
<AccordionWrapper
|
|
27
30
|
className={className}
|
|
28
31
|
open={expanded}
|
|
32
|
+
name={groupName ?? undefined}
|
|
29
33
|
data-component-name={ACCORDION_COMPONENT_NAME}
|
|
30
34
|
data-source={dataSource}
|
|
31
35
|
data-hash={dataHash}
|
|
@@ -37,7 +41,9 @@ export function Accordion({
|
|
|
37
41
|
/>
|
|
38
42
|
{title}
|
|
39
43
|
</AccordionSummary>
|
|
40
|
-
<AccordionBody>
|
|
44
|
+
<AccordionBody>
|
|
45
|
+
<AccordionGroupContext.Provider value={null}>{children}</AccordionGroupContext.Provider>
|
|
46
|
+
</AccordionBody>
|
|
41
47
|
</AccordionWrapper>
|
|
42
48
|
);
|
|
43
49
|
}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useId } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX, PropsWithChildren } from 'react';
|
|
5
5
|
|
|
6
6
|
import { ACCORDION_GROUP_COMPONENT_NAME, ACCORDION_SELECTOR } from '@redocly/theme/core/constants';
|
|
7
|
+
import { AccordionGroupContext } from '@redocly/theme/core/contexts';
|
|
7
8
|
|
|
8
9
|
export type AccordionGroupProps = {
|
|
10
|
+
singleExpanded?: boolean;
|
|
9
11
|
className?: string;
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export function AccordionGroup({
|
|
15
|
+
singleExpanded = false,
|
|
13
16
|
className,
|
|
14
17
|
children,
|
|
15
18
|
}: PropsWithChildren<AccordionGroupProps>): JSX.Element {
|
|
19
|
+
const groupName = useId();
|
|
20
|
+
|
|
16
21
|
return (
|
|
17
22
|
<AccordionGroupWrapper
|
|
18
23
|
className={className}
|
|
19
24
|
data-component-name={ACCORDION_GROUP_COMPONENT_NAME}
|
|
20
25
|
>
|
|
21
|
-
{
|
|
26
|
+
<AccordionGroupContext.Provider value={singleExpanded ? groupName : null}>
|
|
27
|
+
{children}
|
|
28
|
+
</AccordionGroupContext.Provider>
|
|
22
29
|
</AccordionGroupWrapper>
|
|
23
30
|
);
|
|
24
31
|
}
|
|
@@ -3,6 +3,9 @@ import type { MarkdocSchemaWrapper } from '@redocly/theme/markdoc/tags/types';
|
|
|
3
3
|
export const accordionGroup: MarkdocSchemaWrapper = {
|
|
4
4
|
schema: {
|
|
5
5
|
render: 'AccordionGroup',
|
|
6
|
+
attributes: {
|
|
7
|
+
singleExpanded: { type: Boolean, default: false },
|
|
8
|
+
},
|
|
6
9
|
},
|
|
7
10
|
tagName: 'accordion-group',
|
|
8
11
|
};
|