@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.
- package/lib/components/Buttons/ConnectMCPButton.js +11 -1
- package/lib/components/Buttons/variables.d.ts +1 -0
- package/lib/components/Buttons/variables.js +4 -1
- package/lib/components/Catalog/Catalog.js +2 -2
- package/lib/components/JsonViewer/Helpers.js +3 -3
- package/lib/components/MCP/McpConnectionTabs.js +9 -2
- package/lib/components/Portal/Portal.js +37 -5
- package/lib/components/Search/SearchAiActionButtons.js +8 -3
- package/lib/components/Search/SearchDialog.js +10 -0
- package/lib/components/Search/SearchItem.js +1 -1
- package/lib/components/Search/SearchTrigger.js +33 -8
- package/lib/components/Search/variables.js +2 -0
- package/lib/core/constants/mcp.d.ts +2 -2
- package/lib/core/constants/mcp.js +1 -1
- package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
- package/lib/core/contexts/PortalContainerContext.js +7 -0
- package/lib/core/hooks/code-walkthrough/use-code-panel.d.ts +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.d.ts +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough.d.ts +1 -2
- package/lib/core/hooks/code-walkthrough/use-renderable-files.d.ts +1 -1
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
- package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
- package/lib/core/hooks/use-connect-mcp-button.js +1 -0
- package/lib/core/hooks/use-page-actions.d.ts +1 -1
- package/lib/core/hooks/use-page-actions.js +6 -0
- package/lib/core/styles/global.js +1 -0
- package/lib/core/types/code-walkthrough.d.ts +84 -0
- package/lib/core/types/hooks.d.ts +1 -1
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +1 -1
- package/lib/core/utils/download-code-walkthrough.d.ts +1 -1
- package/lib/core/utils/get-code-walkthrough-file-text.d.ts +1 -1
- package/lib/core/utils/match-code-walkthrough-conditions.d.ts +1 -1
- package/lib/core/utils/mcp.js +7 -2
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.d.ts +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.d.ts +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.d.ts +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.d.ts +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeStep.d.ts +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.d.ts +1 -1
- package/lib/markdoc/components/Tabs/TabList.js +8 -6
- package/lib/markdoc/tags/code-walkthrough.d.ts +1 -1
- package/package.json +4 -3
- package/src/components/Buttons/ConnectMCPButton.tsx +13 -2
- package/src/components/Buttons/variables.ts +4 -0
- package/src/components/Catalog/Catalog.tsx +5 -3
- package/src/components/JsonViewer/Helpers.tsx +5 -3
- package/src/components/MCP/McpConnectionTabs.tsx +22 -5
- package/src/components/Portal/Portal.tsx +4 -2
- package/src/components/Search/SearchAiActionButtons.tsx +31 -19
- package/src/components/Search/SearchDialog.tsx +11 -0
- package/src/components/Search/SearchItem.tsx +3 -1
- package/src/components/Search/SearchTrigger.tsx +42 -7
- package/src/components/Search/variables.ts +2 -0
- package/src/core/constants/mcp.ts +1 -1
- package/src/core/contexts/PortalContainerContext.tsx +4 -0
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/code-walkthrough/use-code-panel.ts +1 -1
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +2 -2
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +1 -1
- package/src/core/hooks/code-walkthrough/use-code-walkthrough.ts +3 -3
- package/src/core/hooks/code-walkthrough/use-renderable-files.ts +1 -1
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
- package/src/core/hooks/use-connect-mcp-button.ts +7 -1
- package/src/core/hooks/use-page-actions.ts +17 -0
- package/src/core/styles/global.ts +2 -1
- package/src/core/types/code-walkthrough.ts +103 -0
- package/src/core/types/hooks.ts +1 -0
- package/src/core/types/l10n.ts +6 -0
- package/src/core/types/mcp.ts +1 -1
- package/src/core/utils/download-code-walkthrough.ts +1 -1
- package/src/core/utils/get-code-walkthrough-file-text.ts +1 -1
- package/src/core/utils/match-code-walkthrough-conditions.ts +1 -1
- package/src/core/utils/mcp.ts +8 -2
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelHeader.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelToolbar.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
- package/src/markdoc/components/Tabs/TabList.tsx +9 -12
- 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
|
-
|
|
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
|
|
35
|
-
//
|
|
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
|
|
60
|
-
{isMac ?
|
|
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
|
-
|
|
89
|
-
|
|
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];
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
5
|
-
} from '
|
|
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/
|
|
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';
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
+
};
|
package/src/core/types/hooks.ts
CHANGED
package/src/core/types/l10n.ts
CHANGED
|
@@ -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'
|
package/src/core/types/mcp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { saveAs } from 'file-saver';
|
|
2
2
|
|
|
3
|
-
import type { CodeWalkthroughFile } from '@redocly/
|
|
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/
|
|
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';
|
package/src/core/utils/mcp.ts
CHANGED
|
@@ -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
|
-
|
|
38
|
-
return
|
|
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/
|
|
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/
|
|
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';
|