@redocly/theme 0.70.0-next.6 → 0.70.0-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/lib/components/Buttons/ConnectMCPButton.js +11 -1
  2. package/lib/components/Buttons/variables.d.ts +1 -0
  3. package/lib/components/Buttons/variables.js +4 -1
  4. package/lib/components/Catalog/Catalog.js +2 -2
  5. package/lib/components/JsonViewer/Helpers.js +3 -3
  6. package/lib/components/MCP/McpConnectionTabs.js +9 -2
  7. package/lib/components/Portal/Portal.js +37 -5
  8. package/lib/components/Search/SearchAiActionButtons.js +8 -3
  9. package/lib/components/Search/SearchDialog.js +10 -0
  10. package/lib/components/Search/SearchItem.js +1 -1
  11. package/lib/components/Search/SearchTrigger.js +33 -8
  12. package/lib/components/Search/variables.js +2 -0
  13. package/lib/core/constants/mcp.d.ts +2 -2
  14. package/lib/core/constants/mcp.js +1 -1
  15. package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
  16. package/lib/core/contexts/PortalContainerContext.js +7 -0
  17. package/lib/core/hooks/code-walkthrough/use-code-panel.d.ts +1 -1
  18. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +1 -1
  19. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +1 -1
  20. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.d.ts +1 -1
  21. package/lib/core/hooks/code-walkthrough/use-code-walkthrough.d.ts +1 -2
  22. package/lib/core/hooks/code-walkthrough/use-renderable-files.d.ts +1 -1
  23. package/lib/core/hooks/index.d.ts +1 -0
  24. package/lib/core/hooks/index.js +1 -0
  25. package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
  26. package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
  27. package/lib/core/hooks/use-connect-mcp-button.js +1 -0
  28. package/lib/core/hooks/use-page-actions.d.ts +1 -1
  29. package/lib/core/hooks/use-page-actions.js +6 -0
  30. package/lib/core/styles/global.js +1 -0
  31. package/lib/core/types/code-walkthrough.d.ts +84 -0
  32. package/lib/core/types/hooks.d.ts +1 -1
  33. package/lib/core/types/l10n.d.ts +1 -1
  34. package/lib/core/types/mcp.d.ts +1 -1
  35. package/lib/core/utils/download-code-walkthrough.d.ts +1 -1
  36. package/lib/core/utils/get-code-walkthrough-file-text.d.ts +1 -1
  37. package/lib/core/utils/match-code-walkthrough-conditions.d.ts +1 -1
  38. package/lib/core/utils/mcp.js +7 -2
  39. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.d.ts +1 -1
  40. package/lib/markdoc/components/CodeWalkthrough/CodePanel.d.ts +1 -1
  41. package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.d.ts +1 -1
  42. package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.d.ts +1 -1
  43. package/lib/markdoc/components/CodeWalkthrough/CodeStep.d.ts +1 -1
  44. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.d.ts +1 -1
  45. package/lib/markdoc/components/Tabs/TabList.js +8 -6
  46. package/lib/markdoc/tags/code-walkthrough.d.ts +1 -1
  47. package/package.json +4 -3
  48. package/src/components/Buttons/ConnectMCPButton.tsx +13 -2
  49. package/src/components/Buttons/variables.ts +4 -0
  50. package/src/components/Catalog/Catalog.tsx +5 -3
  51. package/src/components/JsonViewer/Helpers.tsx +5 -3
  52. package/src/components/MCP/McpConnectionTabs.tsx +22 -5
  53. package/src/components/Portal/Portal.tsx +4 -2
  54. package/src/components/Search/SearchAiActionButtons.tsx +31 -19
  55. package/src/components/Search/SearchDialog.tsx +11 -0
  56. package/src/components/Search/SearchItem.tsx +3 -1
  57. package/src/components/Search/SearchTrigger.tsx +42 -7
  58. package/src/components/Search/variables.ts +2 -0
  59. package/src/core/constants/mcp.ts +1 -1
  60. package/src/core/contexts/PortalContainerContext.tsx +4 -0
  61. package/src/core/hooks/__mocks__/index.ts +1 -0
  62. package/src/core/hooks/code-walkthrough/use-code-panel.ts +1 -1
  63. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +2 -2
  64. package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +1 -1
  65. package/src/core/hooks/code-walkthrough/use-code-walkthrough.ts +3 -3
  66. package/src/core/hooks/code-walkthrough/use-renderable-files.ts +1 -1
  67. package/src/core/hooks/index.ts +1 -0
  68. package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
  69. package/src/core/hooks/use-connect-mcp-button.ts +7 -1
  70. package/src/core/hooks/use-page-actions.ts +17 -0
  71. package/src/core/styles/global.ts +2 -1
  72. package/src/core/types/code-walkthrough.ts +103 -0
  73. package/src/core/types/hooks.ts +1 -0
  74. package/src/core/types/l10n.ts +6 -0
  75. package/src/core/types/mcp.ts +1 -1
  76. package/src/core/utils/download-code-walkthrough.ts +1 -1
  77. package/src/core/utils/get-code-walkthrough-file-text.ts +1 -1
  78. package/src/core/utils/match-code-walkthrough-conditions.ts +1 -1
  79. package/src/core/utils/mcp.ts +8 -2
  80. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
  81. package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +1 -1
  82. package/src/markdoc/components/CodeWalkthrough/CodePanelHeader.tsx +1 -1
  83. package/src/markdoc/components/CodeWalkthrough/CodePanelToolbar.tsx +1 -1
  84. package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +1 -1
  85. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
  86. package/src/markdoc/components/Tabs/TabList.tsx +9 -12
  87. package/src/markdoc/tags/code-walkthrough.ts +1 -1
@@ -19,6 +19,7 @@ import {
19
19
  useSearchFilter,
20
20
  useRecentSearches,
21
21
  useModalScrollLock,
22
+ usePersistedQuickFilter,
22
23
  } from '@redocly/theme/core/hooks';
23
24
  import { Tag } from '@redocly/theme/components/Tag/Tag';
24
25
  import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
@@ -81,6 +82,16 @@ export function SearchDialog({
81
82
  onFacetReset,
82
83
  onQuickFilterReset,
83
84
  } = useSearchFilter(filter, setFilter);
85
+ usePersistedQuickFilter({
86
+ filter,
87
+ setFilter,
88
+ facets,
89
+ groupField,
90
+ query,
91
+ isSearchLoading,
92
+ searchError,
93
+ enabled: initialMode === 'search',
94
+ });
84
95
  const { addSearchHistoryItem } = useRecentSearches();
85
96
  const aiSearch = useAiSearch({ filter: filter, product: product?.name });
86
97
  useModalScrollLock(true);
@@ -78,7 +78,9 @@ export function SearchItem({
78
78
  <SearchItemOperationTag color={httpColor}>
79
79
  {document.httpMethod.toUpperCase()}
80
80
  </SearchItemOperationTag>
81
- {highlight.httpPath ? searchHighlight(highlight.httpPath) : document.httpPath}
81
+ <span>
82
+ {highlight.httpPath ? searchHighlight(highlight.httpPath) : document.httpPath}
83
+ </span>
82
84
  </SearchItemOperation>
83
85
  ) : null}
84
86
  <SearchItemTitleWrapper>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import styled from 'styled-components';
2
+ import styled, { keyframes } from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
 
@@ -9,6 +9,9 @@ import { SearchIcon } from '@redocly/theme/icons/SearchIcon/SearchIcon';
9
9
  import { breakpoints, getUserAgent } from '@redocly/theme/core/utils';
10
10
  import { DEFAULT_KEYBOARD_SHORTCUTS } from '@redocly/theme/core/constants';
11
11
 
12
+ const MAC_SHORTCUT_LABEL = '⌘K';
13
+ const SHORTCUT_LABEL = 'Ctrl+K';
14
+
12
15
  export type SearchTriggerProps = {
13
16
  onClick: () => void;
14
17
  className?: string;
@@ -31,8 +34,8 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
31
34
  }
32
35
 
33
36
  mainShortcutKey = mainShortcutKey?.toUpperCase();
34
- // A page can be cached and served to every platform, so the server renders a hidden
35
- // placeholder and the browser picks the label after hydration.
37
+ // A page can be cached and served to every platform, so the server renders an empty
38
+ // slot and the browser picks the label after hydration.
36
39
  const [isMac, setIsMac] = React.useState<boolean | undefined>(undefined);
37
40
  React.useEffect(() => setIsMac(getUserAgent().includes('Mac')), []);
38
41
 
@@ -56,8 +59,12 @@ export function SearchTrigger({ onClick, className }: SearchTriggerProps): JSX.E
56
59
  <SearchIcon />
57
60
  {translate('search.navbar.label', 'Search')}
58
61
  {mainShortcutKey === DEFAULT_KEYBOARD_SHORTCUTS ? (
59
- <SearchTriggerShortcut $isPending={isMac === undefined}>
60
- {isMac ? '⌘K' : 'Ctrl+K'}
62
+ <SearchTriggerShortcut>
63
+ {isMac === undefined ? null : (
64
+ <SearchTriggerShortcutLabel>
65
+ {isMac ? MAC_SHORTCUT_LABEL : SHORTCUT_LABEL}
66
+ </SearchTriggerShortcutLabel>
67
+ )}
61
68
  </SearchTriggerShortcut>
62
69
  ) : (
63
70
  <span>{mainShortcutKey}</span>
@@ -85,8 +92,36 @@ const SearchTriggerButton = styled(Button)`
85
92
  }
86
93
  `;
87
94
 
88
- const SearchTriggerShortcut = styled.span<{ $isPending: boolean }>`
89
- visibility: ${({ $isPending }) => ($isPending ? 'hidden' : 'visible')};
95
+ // The hidden copy of the wider label keeps the slot the same width before and after
96
+ // hydration on every platform, so the trigger does not shift when the label appears.
97
+ const SearchTriggerShortcut = styled.span`
98
+ display: inline-flex;
99
+ flex-direction: column;
100
+ align-items: flex-end;
101
+
102
+ &::after {
103
+ content: '${SHORTCUT_LABEL}';
104
+ height: 0;
105
+ overflow: hidden;
106
+ visibility: hidden;
107
+ }
108
+ `;
109
+
110
+ const shortcutAppear = keyframes`
111
+ from {
112
+ opacity: 0;
113
+ filter: blur(2px);
114
+ transform: translateY(3px);
115
+ }
116
+ `;
117
+
118
+ const SearchTriggerShortcutLabel = styled.span`
119
+ animation: ${shortcutAppear} var(--search-trigger-shortcut-appear-duration)
120
+ var(--search-trigger-shortcut-appear-easing);
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ animation: none;
124
+ }
90
125
  `;
91
126
 
92
127
  const SearchTriggerInput = styled.div`
@@ -145,6 +145,8 @@ export const search = css`
145
145
  --search-trigger-bg-color: var(--navbar-bg-color);
146
146
  --search-trigger-icon-size: 16px;
147
147
  --search-trigger-line-height: var(--line-height-base);
148
+ --search-trigger-shortcut-appear-duration: 300ms;
149
+ --search-trigger-shortcut-appear-easing: ease-out;
148
150
 
149
151
  /**
150
152
  * @tokens AI Search
@@ -4,6 +4,6 @@ export const MCP_ROUTE = '/mcp';
4
4
 
5
5
  export const PUBLIC_MCP_ROUTE = '/mcp-public';
6
6
 
7
- export const MCP_OPTIONS = ['cursor', 'vscode', 'claude-code', 'codex', 'copy'] as const;
7
+ export const MCP_OPTIONS = ['cursor', 'vscode', 'claude', 'claude-code', 'codex', 'copy'] as const;
8
8
 
9
9
  export const DEFAULT_MCP_OPTIONS = [...MCP_OPTIONS];
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+
3
+ /** Where theme portals mount; set it inside a shadow root (the AI assistant web component), else `document.body`. */
4
+ export const PortalContainerContext = createContext<HTMLElement | null>(null);
@@ -24,6 +24,7 @@ export * from '../feedback/use-report-dialog';
24
24
  export * from '../use-product-picker';
25
25
  export * from '../search/use-search-dialog';
26
26
  export * from '../search/use-search-ai-activity';
27
+ export * from '../search/use-persisted-quick-filter';
27
28
  export * from '../use-language-picker';
28
29
  export * from './use-element-size';
29
30
  export * from './use-time-ago';
@@ -4,7 +4,7 @@ import type {
4
4
  CodeWalkthroughFile,
5
5
  CodeWalkthroughNode,
6
6
  CodeWalkthroughConditionsObject,
7
- } from '@redocly/config';
7
+ } from '@redocly/theme/core/types';
8
8
 
9
9
  import { CodeWalkthroughControlsStateContext } from '../../contexts/CodeWalkthrough/CodeWalkthroughControlsContext';
10
10
  import { CodeWalkthroughStepsContext } from '../../contexts/CodeWalkthrough/CodeWalkthroughStepsContext';
@@ -10,7 +10,7 @@ import type {
10
10
  CodeWalkthroughFilterItem,
11
11
  CodeWalkthroughConditionsObject,
12
12
  CodeWalkthroughControlsState,
13
- } from '@redocly/config';
13
+ } from '@redocly/theme/core/types';
14
14
 
15
15
  import { downloadCodeWalkthrough } from '../../utils/download-code-walkthrough';
16
16
  import { matchCodeWalkthroughConditions } from '../../utils/match-code-walkthrough-conditions';
@@ -98,7 +98,7 @@ export function useCodeWalkthroughControls(
98
98
  Object.entries(newState).forEach(([id, control]) => {
99
99
  const existingControl = controlsState[id];
100
100
  if (existingControl && existingControl.type === control.type) {
101
- // @ts-expect-error
101
+ // @ts-expect-error: TS2322 - the spread loses the link between `type` and `value` in the control union
102
102
  newState[id] = {
103
103
  ...control,
104
104
  value: existingControl.value,
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
2
2
  import { useLocation, useNavigate } from 'react-router';
3
3
 
4
- import type { CodeWalkthroughStepAttr } from '@redocly/config';
4
+ import type { CodeWalkthroughStepAttr } from '@redocly/theme/core/types';
5
5
  import type { ActiveStep, WalkthroughStepsState } from '../../types/code-walkthrough';
6
6
  import type { MarkerArea } from '../../types/marker';
7
7
 
@@ -1,9 +1,9 @@
1
1
  import type {
2
+ CodeWalkthroughAttr,
2
3
  CodeWalkthroughFile,
3
4
  CodeWalkthroughStepAttr,
4
- CodeWalkthroughAttr,
5
- } from '@redocly/config';
6
- import type { WalkthroughStepsState } from '../../types/code-walkthrough';
5
+ WalkthroughStepsState,
6
+ } from '../../types/code-walkthrough';
7
7
 
8
8
  import {
9
9
  useCodeWalkthroughControls,
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- import type { CodeWalkthroughFile } from '@redocly/config';
3
+ import type { CodeWalkthroughFile } from '@redocly/theme/core/types';
4
4
 
5
5
  import { findClosestCommonDirectory } from '../../utils/find-closest-common-directory';
6
6
  import { getFileIconByExt } from '../../utils/GetFileIcon';
@@ -31,6 +31,7 @@ export * from './search/use-search-dialog';
31
31
  export * from './search/use-search-filter';
32
32
  export * from './search/use-feedback-tooltip';
33
33
  export * from './search/use-search-ai-activity';
34
+ export * from './search/use-persisted-quick-filter';
34
35
  export * from './use-controlled-state';
35
36
  export * from './use-codeblock-tabs-controls';
36
37
  export * from './code-walkthrough/use-code-walkthrough';
@@ -0,0 +1,125 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import type { Dispatch, SetStateAction } from 'react';
4
+ import type { SearchFacet, SearchFilterItem } from '../../types/search';
5
+
6
+ import { createStore, useStore } from '../use-store';
7
+
8
+ type StoredQuickFilter = Pick<SearchFilterItem, 'field' | 'values'>;
9
+
10
+ const quickFilterStore = createStore<StoredQuickFilter | null>({
11
+ storageKey: 'searchQuickFilter',
12
+ storageType: 'sessionStorage',
13
+ });
14
+
15
+ const findQuickFilter = (filter: SearchFilterItem[]) => filter.find((item) => item.isQuickFilter);
16
+
17
+ const withQuickFilter = (filter: SearchFilterItem[], quickFilter: StoredQuickFilter) => [
18
+ ...filter.filter((item) => !item.isQuickFilter),
19
+ ...(quickFilter.values.length ? [{ ...quickFilter, isQuickFilter: true }] : []),
20
+ ];
21
+
22
+ const groupFacetValues = (facets: SearchFacet[], field: string) => {
23
+ const facet = facets.find((item) => item.field === field);
24
+ const values = facet?.values ?? [];
25
+ return {
26
+ present: facet !== undefined,
27
+ values: values.map((item) => (typeof item === 'string' ? item : item.value)),
28
+ counted: values.some((item) => typeof item !== 'string' && item.isCounterVisible),
29
+ };
30
+ };
31
+
32
+ const isRestorable = (
33
+ stored: StoredQuickFilter | null,
34
+ groupField: string,
35
+ ): stored is StoredQuickFilter =>
36
+ !!stored &&
37
+ stored.field === groupField &&
38
+ Array.isArray(stored.values) &&
39
+ stored.values.length > 0 &&
40
+ stored.values.every((value) => typeof value === 'string');
41
+
42
+ type UsePersistedQuickFilterOptions = {
43
+ filter: SearchFilterItem[];
44
+ setFilter: Dispatch<SetStateAction<SearchFilterItem[]>>;
45
+ facets: SearchFacet[];
46
+ groupField: string;
47
+ query: string;
48
+ isSearchLoading: boolean;
49
+ searchError: string | null;
50
+ enabled?: boolean;
51
+ };
52
+
53
+ /**
54
+ * Remembers the quick filter on `groupField` for the browser tab and restores it on mount unless
55
+ * one is already selected. Restored values are dropped once known not to exist: absent from the
56
+ * full value list, or from the `groupField` facet left by the first search that finishes without
57
+ * error on an empty query (the restored value is that search's filter, so a value without
58
+ * documents is not counted). Does nothing while `enabled` is false.
59
+ */
60
+ export function usePersistedQuickFilter({
61
+ filter,
62
+ setFilter,
63
+ facets,
64
+ groupField,
65
+ query,
66
+ isSearchLoading,
67
+ searchError,
68
+ enabled = true,
69
+ }: UsePersistedQuickFilterOptions): void {
70
+ const [stored, setStored] = useStore(quickFilterStore, null);
71
+ const [initial] = useState(() =>
72
+ enabled && isRestorable(stored, groupField) && !findQuickFilter(filter) ? stored.values : null,
73
+ );
74
+ const phaseRef = useRef<'restoring' | 'validating' | 'done'>(initial ? 'restoring' : 'done');
75
+ const searchedRef = useRef(false);
76
+
77
+ useEffect(() => {
78
+ if (!initial) return;
79
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the filter state belongs to the product's `useSearch` and starts empty on every mount
80
+ setFilter((current) => withQuickFilter(current, { field: groupField, values: initial }));
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- applies only to the initial state
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ if (!enabled) return;
86
+ const quickFilter = findQuickFilter(filter);
87
+ // The commit that starts the restore still sees the empty initial filter.
88
+ if (phaseRef.current === 'restoring') {
89
+ if (!quickFilter) return;
90
+ phaseRef.current = 'validating';
91
+ }
92
+ if (phaseRef.current === 'validating' && initial) {
93
+ if (isSearchLoading) searchedRef.current = true;
94
+ const settled = searchedRef.current && !isSearchLoading && !searchError && !query.trim();
95
+ const { present, values: known, counted } = groupFacetValues(facets, groupField);
96
+ if ((known.length && !counted) || (settled && present)) {
97
+ phaseRef.current = 'done';
98
+ const values = quickFilter?.values.filter(
99
+ (value) => known.includes(value) || !initial.includes(value),
100
+ );
101
+ if (quickFilter && values && values.length !== quickFilter.values.length) {
102
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the facets arrive after the restore
103
+ setFilter((current) => withQuickFilter(current, { field: groupField, values }));
104
+ return;
105
+ }
106
+ }
107
+ }
108
+ const next = quickFilter ? { field: quickFilter.field, values: quickFilter.values } : null;
109
+ if (JSON.stringify(next) !== JSON.stringify(stored)) {
110
+ setStored(next);
111
+ }
112
+ }, [
113
+ enabled,
114
+ filter,
115
+ facets,
116
+ groupField,
117
+ query,
118
+ isSearchLoading,
119
+ searchError,
120
+ initial,
121
+ stored,
122
+ setFilter,
123
+ setStored,
124
+ ]);
125
+ }
@@ -7,7 +7,12 @@ import { useThemeHooks } from './use-theme-hooks';
7
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
- import { generateClaudeCodeCommand, generateCodexCommand, isMCPOption } from '../utils/mcp';
10
+ import {
11
+ generateClaudeCodeCommand,
12
+ generateCodexCommand,
13
+ generateMCPDeepLink,
14
+ isMCPOption,
15
+ } from '../utils/mcp';
11
16
  import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
12
17
 
13
18
  export type McpButtonOptions = {
@@ -68,6 +73,7 @@ export function useConnectMCPButton({
68
73
  const urlMap: Partial<Record<MCPOption, string>> = {
69
74
  cursor: cursorUrl,
70
75
  vscode: vscodeUrl,
76
+ claude: generateMCPDeepLink('claude', connection),
71
77
  };
72
78
 
73
79
  window.open(urlMap[action], '_blank');
@@ -31,6 +31,7 @@ const DEFAULT_ENABLED_ACTIONS = [
31
31
  'claude',
32
32
  'docs-mcp-cursor',
33
33
  'docs-mcp-vscode',
34
+ 'docs-mcp-claude',
34
35
  ] as const;
35
36
 
36
37
  export type PageActionType =
@@ -40,6 +41,7 @@ export type PageActionType =
40
41
  | 'claude'
41
42
  | 'docs-mcp-cursor'
42
43
  | 'docs-mcp-vscode'
44
+ | 'docs-mcp-claude'
43
45
  // Used in OpenAPI docs x-mcp
44
46
  | 'mcp-cursor'
45
47
  | 'mcp-vscode';
@@ -114,6 +116,7 @@ export function usePageActions(
114
116
  const actionHandlers: Record<PageActionType, () => PageAction> = {
115
117
  'docs-mcp-cursor': createMCPHandler('cursor', false),
116
118
  'docs-mcp-vscode': createMCPHandler('vscode', false),
119
+ 'docs-mcp-claude': createMCPHandler('claude', false),
117
120
  'mcp-cursor': createMCPHandler('cursor', true),
118
121
  'mcp-vscode': createMCPHandler('vscode', true),
119
122
 
@@ -261,6 +264,19 @@ function createMCPAction({
261
264
  };
262
265
  }
263
266
 
267
+ if (clientType === 'claude') {
268
+ return {
269
+ ...sharedProps,
270
+ buttonText: translate('page.actions.connectMcp.claude', 'Connect to Claude'),
271
+ title: translate('page.actions.connectMcp.claude', 'Connect to Claude'),
272
+ description: translate(
273
+ 'page.actions.connectMcp.claudeDescription',
274
+ 'Add MCP server to your Claude account',
275
+ ),
276
+ iconComponent: ClaudeIcon,
277
+ };
278
+ }
279
+
264
280
  return {
265
281
  ...sharedProps,
266
282
  buttonText: translate('page.actions.connectMcp.vscode', 'Connect to VS Code'),
@@ -337,6 +353,7 @@ function shouldHideActionByType(
337
353
  return !isPublic;
338
354
  case 'docs-mcp-cursor':
339
355
  case 'docs-mcp-vscode':
356
+ case 'docs-mcp-claude':
340
357
  return Boolean(mcpUrl) || isMcpDisabled;
341
358
  case 'mcp-cursor':
342
359
  case 'mcp-vscode':
@@ -20,7 +20,7 @@ import { checkbox } from '@redocly/theme/icons/CheckboxIcon/variables';
20
20
  import { admonition } from '@redocly/theme/components/Admonition/variables';
21
21
  import { footer } from '@redocly/theme/components/Footer/variables';
22
22
  import { button } from '@redocly/theme/components/Button/variables';
23
- import { aiAssistantButton } from '@redocly/theme/components/Buttons/variables';
23
+ import { aiAssistantButton, connectMcpButton } from '@redocly/theme/components/Buttons/variables';
24
24
  import { navbar } from '@redocly/theme/components/Navbar/variables';
25
25
  import { search } from '@redocly/theme/components/Search/variables';
26
26
  import { menu, mobileMenu } from '@redocly/theme/components/Menu/variables';
@@ -1343,6 +1343,7 @@ export const tokens = css`
1343
1343
  ${breadcrumbs}
1344
1344
  ${button}
1345
1345
  ${aiAssistantButton}
1346
+ ${connectMcpButton}
1346
1347
  ${cards}
1347
1348
  ${catalog}
1348
1349
  ${catalogClassic}
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { MarkerArea } from './marker';
2
3
 
3
4
  export type ActiveStep = string | null;
@@ -13,3 +14,105 @@ export type WalkthroughStepsState = {
13
14
  lockObserver?: React.RefObject<boolean>;
14
15
  filtersElementRef?: React.RefObject<HTMLDivElement | null>;
15
16
  };
17
+
18
+ export type CodeWalkthroughStepAttr = {
19
+ id: string;
20
+ title?: string;
21
+ };
22
+
23
+ export type FilesetsMarkdocAttr = WithConditions<{
24
+ files?: string[];
25
+ downloadAssociatedFiles?: string[];
26
+ }>[];
27
+
28
+ export type InputsMarkdocAttr = {
29
+ [id: string]: WithConditions<{ value: string }>;
30
+ };
31
+
32
+ export type TogglesMarkdocAttr = {
33
+ [id: string]: CodeWalkthroughConditionsObject;
34
+ };
35
+
36
+ export type CodeWalkthroughConditions = {
37
+ [key: string]: string | string[] | undefined | boolean;
38
+ };
39
+
40
+ export type CodeWalkthroughFileset = WithConditions<{
41
+ files?: CodeWalkthroughFile[];
42
+ downloadAssociatedFiles?: CodeWalkthroughFile[];
43
+ }>;
44
+ export type CodeWalkthroughFile = {
45
+ path: string;
46
+ content: CodeWalkthroughNode[];
47
+ basename: string;
48
+ metadata: CodeWalkthroughFileMetadata;
49
+ language: string;
50
+ };
51
+
52
+ export type CodeWalkthroughChunk = {
53
+ start: number;
54
+ children: CodeWalkthroughNode[];
55
+ condition: CodeWalkthroughChunkCondition;
56
+ };
57
+
58
+ export type CodeWalkthroughChunkCondition = WithConditions<{
59
+ steps: string[];
60
+ }>;
61
+
62
+ export type CodeWalkthroughNode = CodeWalkthroughChunk | string;
63
+
64
+ export type CodeWalkthroughFileMetadata = {
65
+ steps: string[];
66
+ };
67
+
68
+ export type CodeWalkthroughFilter = WithConditions<{
69
+ id: string;
70
+ label?: string;
71
+ items: CodeWalkthroughFilterItem[];
72
+ }>;
73
+
74
+ export type CodeWalkthroughFilterItem = WithConditions<{
75
+ value: string;
76
+ }>;
77
+
78
+ export type CodeWalkthroughConditionsObject = {
79
+ when?: CodeWalkthroughConditions;
80
+ unless?: CodeWalkthroughConditions;
81
+ };
82
+
83
+ export type WithConditions<T> = T & CodeWalkthroughConditionsObject;
84
+
85
+ export type CodeWalkthroughControls = {
86
+ toggle: boolean;
87
+ input: string;
88
+ filter: string;
89
+ };
90
+
91
+ export type CodeWalkthroughControlState = WithConditions<
92
+ | {
93
+ value: string;
94
+ render: boolean;
95
+ type: 'input';
96
+ }
97
+ | {
98
+ value: string;
99
+ render: boolean;
100
+ type: 'filter';
101
+ }
102
+ | {
103
+ value: boolean;
104
+ render: boolean;
105
+ type: 'toggle';
106
+ }
107
+ >;
108
+ export type CodeWalkthroughControlsState = Record<string, CodeWalkthroughControlState>;
109
+
110
+ export type CodeWalkthroughAttr = {
111
+ steps: CodeWalkthroughStepAttr[];
112
+ filters: Record<string, CodeWalkthroughFilter>;
113
+ filesets: CodeWalkthroughFileset[];
114
+ preview: ReactNode[];
115
+ inputs: InputsMarkdocAttr;
116
+ toggles: TogglesMarkdocAttr;
117
+ __idx: number;
118
+ };
@@ -191,6 +191,7 @@ export type ThemeHooks = {
191
191
  | 'startSpan'
192
192
  | 'constructCloudEvent'
193
193
  | 'sendToOtelService'
194
+ | 'withTracer'
194
195
  > & {
195
196
  send<TEventType extends AsyncApiRealmUI.EventType>(
196
197
  event: TEventType,
@@ -124,6 +124,9 @@ export type TranslationKey =
124
124
  | 'search.ai.error.header.forbidden'
125
125
  | 'search.ai.error.header.unauthorized'
126
126
  | 'search.ai.feedback.more'
127
+ | 'search.ai.actions.copy'
128
+ | 'search.ai.actions.like'
129
+ | 'search.ai.actions.dislike'
127
130
  | 'search.searchItem.deprecated'
128
131
  | 'search.searchItem.enum'
129
132
  | 'search.searchItem.example'
@@ -149,6 +152,7 @@ export type TranslationKey =
149
152
  | 'page.mcpNotAllowed.snippets.terminal.hint'
150
153
  | 'page.mcpNotAllowed.snippets.claudeCode.headline'
151
154
  | 'page.mcpNotAllowed.snippets.claudeDesktop.headline'
155
+ | 'page.mcpNotAllowed.snippets.claudeDesktop.accountHint'
152
156
  | 'page.mcpNotAllowed.snippets.claudeDesktop.instruction'
153
157
  | 'page.mcpNotAllowed.snippets.chatgptApp.headline'
154
158
  | 'page.mcpNotAllowed.snippets.chatgptApp.instruction'
@@ -296,6 +300,8 @@ export type TranslationKey =
296
300
  | 'page.actions.connectMcp.cursorDescription'
297
301
  | 'page.actions.connectMcp.vscode'
298
302
  | 'page.actions.connectMcp.vscodeDescription'
303
+ | 'page.actions.connectMcp.claude'
304
+ | 'page.actions.connectMcp.claudeDescription'
299
305
  | 'page.actions.connectMcp.claudeCode'
300
306
  | 'page.actions.connectMcp.claudeCodeDescription'
301
307
  | 'page.actions.connectMcp.codex'
@@ -1,6 +1,6 @@
1
1
  import type { MCP_OPTIONS } from '../constants/mcp';
2
2
 
3
- export type MCPClientType = 'cursor' | 'vscode';
3
+ export type MCPClientType = 'cursor' | 'vscode' | 'claude';
4
4
 
5
5
  export type MCPOption = (typeof MCP_OPTIONS)[number];
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { saveAs } from 'file-saver';
2
2
 
3
- import type { CodeWalkthroughFile } from '@redocly/config';
3
+ import type { CodeWalkthroughFile } from '@redocly/theme/core/types';
4
4
 
5
5
  import { removeLeadingSlash } from './urls';
6
6
  import { findClosestCommonDirectory } from './find-closest-common-directory';
@@ -1,4 +1,4 @@
1
- import type { CodeWalkthroughFile, CodeWalkthroughNode } from '@redocly/config';
1
+ import type { CodeWalkthroughFile, CodeWalkthroughNode } from '@redocly/theme/core/types';
2
2
 
3
3
  import { matchCodeWalkthroughConditions } from './match-code-walkthrough-conditions';
4
4
  import { replaceInputsWithValue } from './replace-inputs-with-value';
@@ -1,4 +1,4 @@
1
- import type { CodeWalkthroughConditionsObject } from '@redocly/config';
1
+ import type { CodeWalkthroughConditionsObject } from '@redocly/theme/core/types';
2
2
 
3
3
  import { isObject } from './type-guards';
4
4
 
@@ -25,6 +25,12 @@ function generateVSCodeMCPDeepLink(config: McpConnectionParams): string {
25
25
  return `vscode:mcp/install?${encodedConfig}`;
26
26
  }
27
27
 
28
+ function generateClaudeConnectorLink(config: McpConnectionParams): string {
29
+ const connectorName = encodeURIComponent(config.serverName);
30
+ const connectorUrl = encodeURIComponent(config.url);
31
+ return `https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=${connectorName}&connectorUrl=${connectorUrl}`;
32
+ }
33
+
28
34
  export function generateMCPDeepLink(
29
35
  clientType: MCPClientType,
30
36
  config: McpConnectionParams,
@@ -34,8 +40,8 @@ export function generateMCPDeepLink(
34
40
  return generateCursorMCPDeepLink(config);
35
41
  case 'vscode':
36
42
  return generateVSCodeMCPDeepLink(config);
37
- default:
38
- return generateCursorMCPDeepLink(config);
43
+ case 'claude':
44
+ return generateClaudeConnectorLink(config);
39
45
  }
40
46
  }
41
47
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
- import type { CodeWalkthroughFilter } from '@redocly/config';
4
+ import type { CodeWalkthroughFilter } from '@redocly/theme/core/types';
5
5
 
6
6
  import { Tag, ContentWrapper as TagContentWrapper } from '@redocly/theme/components/Tag/Tag';
7
7
 
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
- import type { CodeWalkthroughFile } from '@redocly/config';
5
+ import type { CodeWalkthroughFile } from '@redocly/theme/core/types';
6
6
 
7
7
  import { useCodePanel } from '@redocly/theme/core/hooks';
8
8
  import { CodeWalkthroughControlsStateContext } from '@redocly/theme/core/contexts';