@redocly/theme 0.59.0-rc.2 → 0.60.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -1
- package/lib/components/Buttons/AIAssistantButton.js +6 -2
- package/lib/components/Buttons/ConnectMCPButton.d.ts +8 -0
- package/lib/components/Buttons/ConnectMCPButton.js +145 -0
- package/lib/components/Buttons/variables.d.ts +1 -0
- package/lib/components/Buttons/variables.js +42 -2
- package/lib/components/Catalog/CatalogEntity/CatalogEntityInfoBar.js +1 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntityIcon.js +2 -1
- package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +4 -0
- package/lib/components/Catalog/CatalogTagsWithTooltip.js +1 -1
- package/lib/components/Catalog/variables.js +1 -1
- package/lib/components/Dropdown/Dropdown.d.ts +16 -2
- package/lib/components/Dropdown/Dropdown.js +5 -5
- package/lib/components/Link/Link.d.ts +1 -0
- package/lib/components/Menu/MenuItem.js +1 -1
- package/lib/components/Navbar/NavbarItem.js +3 -3
- package/lib/components/PageActions/PageActions.js +4 -1
- package/lib/components/PageActions/variables.js +2 -0
- package/lib/components/Search/FilterFields/SearchFilterFieldTags.js +1 -2
- package/lib/components/Search/SearchAiActionButtons.d.ts +10 -0
- package/lib/components/Search/SearchAiActionButtons.js +43 -0
- package/lib/components/Search/SearchAiConversationInput.d.ts +3 -1
- package/lib/components/Search/SearchAiConversationInput.js +39 -7
- package/lib/components/Search/SearchAiDialog.d.ts +3 -6
- package/lib/components/Search/SearchAiDialog.js +20 -9
- package/lib/components/Search/SearchAiMessage.d.ts +9 -5
- package/lib/components/Search/SearchAiMessage.js +146 -22
- package/lib/components/Search/SearchAiNegativeFeedbackForm.d.ts +8 -0
- package/lib/components/Search/SearchAiNegativeFeedbackForm.js +169 -0
- package/lib/components/Search/SearchDialog.js +36 -5
- package/lib/components/Search/SearchGroups.js +2 -2
- package/lib/components/Search/variables.js +36 -64
- package/lib/components/Segmented/Segmented.d.ts +1 -8
- package/lib/components/Segmented/Segmented.js +3 -1
- package/lib/components/Select/SelectInput.js +1 -1
- package/lib/components/Select/variables.js +2 -2
- package/lib/components/Tag/Tag.d.ts +2 -1
- package/lib/components/Tag/Tag.js +66 -17
- package/lib/components/Tag/variables.dark.js +135 -36
- package/lib/components/Tag/variables.js +78 -61
- package/lib/config.d.ts +2 -2
- package/lib/core/constants/index.d.ts +1 -0
- package/lib/core/constants/index.js +1 -0
- package/lib/core/constants/mcp.d.ts +1 -0
- package/lib/core/constants/mcp.js +5 -0
- package/lib/core/constants/search.d.ts +5 -4
- package/lib/core/constants/search.js +4 -5
- package/lib/core/hooks/index.d.ts +3 -0
- package/lib/core/hooks/index.js +3 -0
- package/lib/core/hooks/menu/use-nested-menu.js +1 -1
- package/lib/core/hooks/search/use-feedback-tooltip.d.ts +6 -0
- package/lib/core/hooks/search/use-feedback-tooltip.js +26 -0
- package/lib/core/hooks/use-connect-mcp-button.d.ts +13 -0
- package/lib/core/hooks/use-connect-mcp-button.js +50 -0
- package/lib/core/hooks/use-mcp-config.d.ts +9 -0
- package/lib/core/hooks/use-mcp-config.js +27 -0
- package/lib/core/hooks/use-page-actions.d.ts +1 -1
- package/lib/core/hooks/use-page-actions.js +99 -119
- package/lib/core/hooks/use-product-picker.js +2 -1
- package/lib/core/hooks/use-tabs.d.ts +3 -2
- package/lib/core/hooks/use-tabs.js +115 -57
- package/lib/core/hooks/use-telemetry-fallback.d.ts +10 -8
- package/lib/core/hooks/use-telemetry-fallback.js +10 -8
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/styles/dark.js +4 -0
- package/lib/core/styles/global.js +5 -0
- package/lib/core/types/hooks.d.ts +2 -2
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/index.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/mcp.d.ts +6 -0
- package/lib/core/types/mcp.js +3 -0
- package/lib/core/types/search.d.ts +11 -4
- package/lib/core/types/search.js +6 -0
- package/lib/core/types/segmented.d.ts +12 -0
- package/lib/core/types/segmented.js +3 -0
- package/lib/core/utils/frontmatter-translate.d.ts +6 -0
- package/lib/core/utils/frontmatter-translate.js +14 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.d.ts +2 -0
- package/lib/core/utils/mcp.js +31 -0
- package/lib/icons/AiStarsGradientIcon/AiStarsGradientIcon.js +44 -4
- package/lib/icons/AiStarsIcon/AiStarsIcon.js +11 -2
- package/lib/icons/ConnectIcon/ConnectIcon.d.ts +9 -0
- package/lib/icons/ConnectIcon/ConnectIcon.js +17 -0
- package/lib/icons/CubeIcon/CubeIcon.d.ts +9 -0
- package/lib/icons/CubeIcon/CubeIcon.js +17 -0
- package/lib/icons/HashtagIcon/HashtagIcon.d.ts +9 -0
- package/lib/icons/HashtagIcon/HashtagIcon.js +22 -0
- package/lib/icons/RedoclyIcon/RedoclyIcon.js +4 -7
- package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.d.ts +9 -0
- package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.js +34 -0
- package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.d.ts +9 -0
- package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.js +34 -0
- package/lib/icons/VSCodeIcon/VSCodeIcon.d.ts +9 -0
- package/lib/icons/VSCodeIcon/VSCodeIcon.js +17 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/markdoc/components/Cards/Card.js +1 -28
- package/lib/markdoc/components/ConnectMCP/ConnectMCP.d.ts +8 -0
- package/lib/markdoc/components/ConnectMCP/ConnectMCP.js +19 -0
- package/lib/markdoc/components/Tabs/TabList.d.ts +3 -1
- package/lib/markdoc/components/Tabs/TabList.js +197 -47
- package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -1
- package/lib/markdoc/components/Tabs/Tabs.js +57 -12
- package/lib/markdoc/components/default.d.ts +1 -0
- package/lib/markdoc/components/default.js +1 -0
- package/lib/markdoc/default.d.ts +6 -0
- package/lib/markdoc/default.js +2 -0
- package/lib/markdoc/tags/card.js +0 -1
- package/lib/markdoc/tags/connect-mcp.d.ts +2 -0
- package/lib/markdoc/tags/connect-mcp.js +27 -0
- package/package.json +6 -6
- package/src/components/Buttons/AIAssistantButton.tsx +6 -2
- package/src/components/Buttons/ConnectMCPButton.tsx +180 -0
- package/src/components/Buttons/variables.ts +42 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityInfoBar.tsx +1 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntityIcon.tsx +2 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +5 -0
- package/src/components/Catalog/CatalogTagsWithTooltip.tsx +1 -5
- package/src/components/Catalog/variables.ts +1 -1
- package/src/components/Dropdown/Dropdown.tsx +84 -79
- package/src/components/Link/Link.tsx +1 -0
- package/src/components/Menu/MenuItem.tsx +1 -0
- package/src/components/Navbar/NavbarItem.tsx +6 -5
- package/src/components/PageActions/PageActions.tsx +5 -1
- package/src/components/PageActions/variables.ts +2 -0
- package/src/components/Search/FilterFields/SearchFilterFieldTags.tsx +3 -3
- package/src/components/Search/SearchAiActionButtons.tsx +76 -0
- package/src/components/Search/SearchAiConversationInput.tsx +61 -18
- package/src/components/Search/SearchAiDialog.tsx +52 -23
- package/src/components/Search/SearchAiMessage.tsx +172 -43
- package/src/components/Search/SearchAiNegativeFeedbackForm.tsx +210 -0
- package/src/components/Search/SearchDialog.tsx +49 -13
- package/src/components/Search/SearchGroups.tsx +2 -0
- package/src/components/Search/variables.ts +36 -64
- package/src/components/Segmented/Segmented.tsx +15 -20
- package/src/components/Select/SelectInput.tsx +1 -0
- package/src/components/Select/variables.ts +2 -2
- package/src/components/Tag/Tag.tsx +35 -19
- package/src/components/Tag/variables.dark.ts +135 -36
- package/src/components/Tag/variables.ts +78 -61
- package/src/config.ts +2 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/constants/mcp.ts +1 -0
- package/src/core/constants/search.ts +8 -4
- package/src/core/hooks/index.ts +3 -0
- package/src/core/hooks/menu/use-nested-menu.ts +2 -2
- package/src/core/hooks/search/use-feedback-tooltip.ts +32 -0
- package/src/core/hooks/use-connect-mcp-button.ts +79 -0
- package/src/core/hooks/use-mcp-config.ts +43 -0
- package/src/core/hooks/use-page-actions.ts +141 -150
- package/src/core/hooks/use-product-picker.ts +2 -1
- package/src/core/hooks/use-tabs.ts +168 -86
- package/src/core/hooks/use-telemetry-fallback.ts +10 -8
- package/src/core/openapi/index.ts +1 -0
- package/src/core/styles/dark.ts +4 -0
- package/src/core/styles/global.ts +6 -1
- package/src/core/types/hooks.ts +5 -1
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +13 -0
- package/src/core/types/mcp.ts +8 -0
- package/src/core/types/search.ts +13 -4
- package/src/core/types/segmented.ts +14 -0
- package/src/core/utils/frontmatter-translate.ts +9 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +34 -0
- package/src/icons/AiStarsGradientIcon/AiStarsGradientIcon.tsx +13 -4
- package/src/icons/AiStarsIcon/AiStarsIcon.tsx +11 -2
- package/src/icons/ConnectIcon/ConnectIcon.tsx +27 -0
- package/src/icons/CubeIcon/CubeIcon.tsx +27 -0
- package/src/icons/HashtagIcon/HashtagIcon.tsx +23 -0
- package/src/icons/RedoclyIcon/RedoclyIcon.tsx +4 -22
- package/src/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.tsx +38 -0
- package/src/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.tsx +35 -0
- package/src/icons/VSCodeIcon/VSCodeIcon.tsx +29 -0
- package/src/index.ts +1 -2
- package/src/markdoc/components/Cards/Card.tsx +1 -28
- package/src/markdoc/components/ConnectMCP/ConnectMCP.tsx +28 -0
- package/src/markdoc/components/Tabs/TabList.tsx +312 -105
- package/src/markdoc/components/Tabs/Tabs.tsx +136 -11
- package/src/markdoc/components/default.ts +1 -0
- package/src/markdoc/default.ts +2 -0
- package/src/markdoc/tags/card.ts +0 -1
- package/src/markdoc/tags/connect-mcp.ts +25 -0
- package/lib/components/OpenApiDocs/hooks/AdditionalOverviewInfo.d.ts +0 -1
- package/lib/components/OpenApiDocs/hooks/AdditionalOverviewInfo.js +0 -11
- package/lib/components/OpenApiDocs/hooks/AfterOpenApiDescription.d.ts +0 -1
- package/lib/components/OpenApiDocs/hooks/AfterOpenApiDescription.js +0 -5
- package/lib/ext/process-scorecard.d.ts +0 -5
- package/lib/ext/process-scorecard.js +0 -11
- package/src/components/OpenApiDocs/hooks/AdditionalOverviewInfo.tsx +0 -9
- package/src/components/OpenApiDocs/hooks/AfterOpenApiDescription.tsx +0 -1
- package/src/ext/process-scorecard.ts +0 -13
|
@@ -13,39 +13,75 @@ type Tabs = {
|
|
|
13
13
|
overflow: number[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
type UseTabsReturn = {
|
|
17
|
+
setTabRef: (element: HTMLButtonElement | null, index: number) => void;
|
|
18
|
+
onTabClick: (labelOrIndex: string | number) => void;
|
|
19
|
+
handleKeyboard: (event: React.KeyboardEvent, index: number) => void;
|
|
20
|
+
visibleTabs: number[];
|
|
21
|
+
overflowTabs: number[];
|
|
22
|
+
isReady: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type UseActiveTabProps = {
|
|
26
|
+
initialTab: string;
|
|
27
|
+
tabsId?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
16
30
|
const MORE_BUTTON_WIDTH = 80;
|
|
17
31
|
const TABS_GAP = 8;
|
|
18
32
|
|
|
19
|
-
export function useTabs({
|
|
33
|
+
export function useTabs({
|
|
34
|
+
activeTab,
|
|
35
|
+
onTabChange,
|
|
36
|
+
totalTabs,
|
|
37
|
+
containerRef,
|
|
38
|
+
}: UseTabsProps): UseTabsReturn {
|
|
20
39
|
const [tabs, setTabs] = useState<Tabs>({
|
|
21
40
|
visible: Array.from({ length: totalTabs }, (_, i) => i),
|
|
22
41
|
overflow: [],
|
|
23
42
|
});
|
|
24
|
-
|
|
43
|
+
const [isReady, setIsReady] = useState(false);
|
|
44
|
+
const isFirstCalculation = useRef(true);
|
|
25
45
|
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
26
46
|
const tabWidthsRef = useRef<number[]>([]);
|
|
27
47
|
const tabLabelsRef = useRef<string[]>([]);
|
|
48
|
+
const activeTabRef = useRef(activeTab);
|
|
49
|
+
const calculateVisibleTabsRef = useRef<(() => void) | null>(null);
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
// Synchronously update ref before any callbacks or effects run
|
|
52
|
+
activeTabRef.current = activeTab;
|
|
30
53
|
|
|
31
|
-
const setTabRef = useCallback(
|
|
32
|
-
|
|
54
|
+
const setTabRef = useCallback(
|
|
55
|
+
(element: HTMLButtonElement | null, index: number) => {
|
|
56
|
+
tabRefs.current[index] = element;
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
const width = element?.offsetWidth;
|
|
59
|
+
if (width) {
|
|
60
|
+
tabWidthsRef.current[index] = width;
|
|
61
|
+
}
|
|
38
62
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}, []);
|
|
63
|
+
const label = element?.getAttribute('data-label');
|
|
64
|
+
if (label) {
|
|
65
|
+
tabLabelsRef.current[index] = label;
|
|
66
|
+
}
|
|
44
67
|
|
|
45
|
-
|
|
68
|
+
// Trigger calculation once all tabs are registered
|
|
69
|
+
if (
|
|
70
|
+
isFirstCalculation.current &&
|
|
71
|
+
tabWidthsRef.current.length >= totalTabs &&
|
|
72
|
+
tabLabelsRef.current.length >= totalTabs &&
|
|
73
|
+
calculateVisibleTabsRef.current
|
|
74
|
+
) {
|
|
75
|
+
requestAnimationFrame(calculateVisibleTabsRef.current);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[totalTabs],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const focusTab = useCallback((index: number) => {
|
|
46
82
|
const currentElement = tabRefs.current[index];
|
|
47
83
|
currentElement?.focus();
|
|
48
|
-
};
|
|
84
|
+
}, []);
|
|
49
85
|
|
|
50
86
|
const onTabSelect = useCallback(
|
|
51
87
|
(index: number) => {
|
|
@@ -53,7 +89,7 @@ export function useTabs({ activeTab, onTabChange, totalTabs, containerRef }: Use
|
|
|
53
89
|
const label = tabRefs.current[index]?.getAttribute('data-label');
|
|
54
90
|
if (label) onTabChange(label);
|
|
55
91
|
},
|
|
56
|
-
[onTabChange],
|
|
92
|
+
[onTabChange, focusTab],
|
|
57
93
|
);
|
|
58
94
|
|
|
59
95
|
const handleKeyboard = useCallback(
|
|
@@ -77,30 +113,19 @@ export function useTabs({ activeTab, onTabChange, totalTabs, containerRef }: Use
|
|
|
77
113
|
[totalTabs, onTabSelect],
|
|
78
114
|
);
|
|
79
115
|
|
|
80
|
-
const replaceLastVisibleTabWithClickedOverflowTab = useCallback(
|
|
81
|
-
(
|
|
82
|
-
const { visible: visibleTabs, overflow: overflowTabs } =
|
|
83
|
-
|
|
84
|
-
// Indexes of visible tabs should be sorted(asc), to replace the last visible tab with the clicked tab
|
|
85
|
-
const newVisibleTabs = [...visibleTabs].sort((a, b) => a - b);
|
|
86
|
-
const newOverflowTabs = [...overflowTabs];
|
|
87
|
-
|
|
88
|
-
const clickedIdxInOverflow = newOverflowTabs.indexOf(clickedIndex);
|
|
89
|
-
if (clickedIdxInOverflow !== -1) {
|
|
90
|
-
const lastVisible = newVisibleTabs[newVisibleTabs.length - 1];
|
|
91
|
-
newOverflowTabs.splice(clickedIdxInOverflow, 1);
|
|
92
|
-
newOverflowTabs.unshift(lastVisible);
|
|
93
|
-
newVisibleTabs.splice(newVisibleTabs.length - 1, 1);
|
|
94
|
-
newVisibleTabs.unshift(clickedIndex);
|
|
95
|
-
}
|
|
116
|
+
const replaceLastVisibleTabWithClickedOverflowTab = useCallback((clickedIndex: number) => {
|
|
117
|
+
setTabs((prevTabs) => {
|
|
118
|
+
const { visible: visibleTabs, overflow: overflowTabs } = prevTabs;
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
const sortedVisible = [...visibleTabs].sort((a, b) => a - b);
|
|
121
|
+
const lastVisible = sortedVisible[sortedVisible.length - 1];
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
visible: visibleTabs.map((idx) => (idx === lastVisible ? clickedIndex : idx)),
|
|
125
|
+
overflow: overflowTabs.map((idx) => (idx === clickedIndex ? lastVisible : idx)),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
}, []);
|
|
104
129
|
|
|
105
130
|
const onTabClick = useCallback(
|
|
106
131
|
(labelOrIndex: string | number) => {
|
|
@@ -111,18 +136,18 @@ export function useTabs({ activeTab, onTabChange, totalTabs, containerRef }: Use
|
|
|
111
136
|
|
|
112
137
|
if (clickedIndex === -1) return;
|
|
113
138
|
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
139
|
+
const label = tabLabelsRef.current[clickedIndex];
|
|
140
|
+
if (!label) return;
|
|
141
|
+
|
|
142
|
+
// If this is an overflow tab, replace it with a visible one
|
|
143
|
+
if (tabs.overflow.includes(clickedIndex)) {
|
|
116
144
|
replaceLastVisibleTabWithClickedOverflowTab(clickedIndex);
|
|
117
145
|
}
|
|
118
146
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
onTabChange(label);
|
|
122
|
-
focusTab(clickedIndex);
|
|
123
|
-
}
|
|
147
|
+
onTabChange(label);
|
|
148
|
+
focusTab(clickedIndex);
|
|
124
149
|
},
|
|
125
|
-
[
|
|
150
|
+
[tabs.overflow, onTabChange, replaceLastVisibleTabWithClickedOverflowTab, focusTab],
|
|
126
151
|
);
|
|
127
152
|
|
|
128
153
|
const calculateVisibleTabs = useCallback(() => {
|
|
@@ -131,87 +156,141 @@ export function useTabs({ activeTab, onTabChange, totalTabs, containerRef }: Use
|
|
|
131
156
|
|
|
132
157
|
const containerWidth = container.offsetWidth;
|
|
133
158
|
const tabWidths = tabWidthsRef.current;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
159
|
+
const tabLabels = tabLabelsRef.current;
|
|
160
|
+
|
|
161
|
+
// Wait until all tabs are registered before calculating
|
|
162
|
+
if (tabWidths.length < totalTabs || tabLabels.length < totalTabs) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check if container has proper width (not zero)
|
|
167
|
+
if (containerWidth === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Find active tab index by label in tabLabelsRef, not by DOM element
|
|
172
|
+
// because tab might not be rendered if it's in overflow
|
|
173
|
+
const activeTabIndex = tabLabels.findIndex((label) => label === activeTabRef.current);
|
|
137
174
|
|
|
138
|
-
// Active tab should always be visible, so we include it at the beginning of the array
|
|
139
175
|
let tabsWidth = activeTabIndex !== -1 ? tabWidths[activeTabIndex] : 0;
|
|
140
|
-
const
|
|
141
|
-
const
|
|
176
|
+
const visibleTabs = activeTabIndex !== -1 ? [activeTabIndex] : [];
|
|
177
|
+
const overflowTabs = [];
|
|
142
178
|
|
|
143
179
|
for (let i = 0; i < tabWidths.length; i++) {
|
|
144
|
-
|
|
145
|
-
if (i === activeTabIndex) {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
180
|
+
if (i === activeTabIndex) continue;
|
|
148
181
|
|
|
149
182
|
const tabWidthWithGap = tabWidths[i] + TABS_GAP;
|
|
150
183
|
const projectedWidth = tabsWidth + tabWidthWithGap;
|
|
151
184
|
|
|
152
185
|
if (projectedWidth <= containerWidth) {
|
|
153
|
-
|
|
186
|
+
visibleTabs.push(i);
|
|
154
187
|
tabsWidth += tabWidthWithGap;
|
|
155
188
|
} else {
|
|
156
|
-
|
|
189
|
+
overflowTabs.push(i);
|
|
157
190
|
}
|
|
158
191
|
}
|
|
159
192
|
|
|
160
|
-
if (
|
|
193
|
+
if (overflowTabs.length > 0) {
|
|
161
194
|
tabsWidth += MORE_BUTTON_WIDTH;
|
|
162
195
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (removed !== undefined) {
|
|
167
|
-
|
|
196
|
+
while (tabsWidth > containerWidth && visibleTabs.length > 1) {
|
|
197
|
+
const removed = visibleTabs.pop();
|
|
198
|
+
// Never remove the active tab - it should always stay visible or be the last one
|
|
199
|
+
if (removed !== undefined && removed !== activeTabIndex) {
|
|
200
|
+
overflowTabs.unshift(removed);
|
|
168
201
|
tabsWidth -= tabWidths[removed];
|
|
202
|
+
} else if (removed === activeTabIndex) {
|
|
203
|
+
// Put it back if we accidentally removed the active tab
|
|
204
|
+
visibleTabs.push(removed);
|
|
205
|
+
break;
|
|
169
206
|
}
|
|
170
207
|
}
|
|
208
|
+
|
|
209
|
+
// If even with only the active tab visible, it doesn't fit with More button,
|
|
210
|
+
// move all tabs to overflow (show only dropdown)
|
|
211
|
+
if (tabsWidth > containerWidth && visibleTabs.length === 1) {
|
|
212
|
+
overflowTabs.unshift(...visibleTabs);
|
|
213
|
+
visibleTabs.length = 0;
|
|
214
|
+
}
|
|
171
215
|
}
|
|
172
216
|
|
|
173
217
|
setTabs({
|
|
174
|
-
visible,
|
|
175
|
-
overflow,
|
|
218
|
+
visible: visibleTabs,
|
|
219
|
+
overflow: overflowTabs,
|
|
176
220
|
});
|
|
177
|
-
}, [containerRef, activeTab]);
|
|
178
221
|
|
|
222
|
+
// Set ready state on first calculation
|
|
223
|
+
if (isFirstCalculation.current) {
|
|
224
|
+
isFirstCalculation.current = false;
|
|
225
|
+
setIsReady(true);
|
|
226
|
+
}
|
|
227
|
+
}, [containerRef, totalTabs]);
|
|
228
|
+
|
|
229
|
+
// Store calculateVisibleTabs in ref for use in setTabRef
|
|
230
|
+
calculateVisibleTabsRef.current = calculateVisibleTabs;
|
|
231
|
+
|
|
232
|
+
// Reset isFirstCalculation when totalTabs changes (new page/tabs)
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
isFirstCalculation.current = true;
|
|
235
|
+
setIsReady(false);
|
|
236
|
+
// Clear refs so we wait for new tabs to register
|
|
237
|
+
tabWidthsRef.current = [];
|
|
238
|
+
tabLabelsRef.current = [];
|
|
239
|
+
}, [totalTabs]);
|
|
240
|
+
|
|
241
|
+
// Call calculateVisibleTabs on first render and resize
|
|
179
242
|
useEffect(() => {
|
|
180
|
-
|
|
243
|
+
const container = containerRef?.current;
|
|
244
|
+
if (!container) return;
|
|
245
|
+
|
|
246
|
+
let resizeTimeout: number | null = null;
|
|
247
|
+
|
|
248
|
+
// Use ResizeObserver to wait until container has proper size
|
|
249
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
250
|
+
if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
|
|
251
|
+
resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
resizeObserver.observe(container);
|
|
181
255
|
|
|
182
|
-
let resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
|
|
183
256
|
const handleResize = () => {
|
|
184
|
-
if (resizeTimeout)
|
|
185
|
-
cancelAnimationFrame(resizeTimeout);
|
|
186
|
-
}
|
|
257
|
+
if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
|
|
187
258
|
resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
|
|
188
259
|
};
|
|
189
260
|
|
|
190
261
|
window.addEventListener('resize', handleResize);
|
|
262
|
+
|
|
191
263
|
return () => {
|
|
264
|
+
resizeObserver.disconnect();
|
|
192
265
|
window.removeEventListener('resize', handleResize);
|
|
193
|
-
cancelAnimationFrame(resizeTimeout);
|
|
266
|
+
if (resizeTimeout) cancelAnimationFrame(resizeTimeout);
|
|
194
267
|
};
|
|
195
268
|
}, [containerRef, totalTabs, calculateVisibleTabs]);
|
|
196
269
|
|
|
270
|
+
// Recalculate when activeTab changes to ensure it's visible
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!containerRef?.current || isFirstCalculation.current) return;
|
|
273
|
+
requestAnimationFrame(calculateVisibleTabs);
|
|
274
|
+
}, [activeTab, containerRef, calculateVisibleTabs]);
|
|
275
|
+
|
|
197
276
|
return {
|
|
198
277
|
setTabRef,
|
|
199
278
|
onTabClick,
|
|
200
279
|
handleKeyboard,
|
|
201
280
|
visibleTabs: tabs.visible,
|
|
202
281
|
overflowTabs: tabs.overflow,
|
|
203
|
-
|
|
282
|
+
isReady,
|
|
204
283
|
};
|
|
205
284
|
}
|
|
206
285
|
|
|
207
|
-
type UseActiveTabProps = {
|
|
208
|
-
initialTab: string;
|
|
209
|
-
tabsId?: string;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
286
|
export const useActiveTab = ({ initialTab, tabsId }: UseActiveTabProps) => {
|
|
213
287
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
214
|
-
const
|
|
288
|
+
const initialTabValue = useMemo(
|
|
289
|
+
() => getInitialTab({ initialTab, searchParams, tabsId }),
|
|
290
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
291
|
+
[],
|
|
292
|
+
);
|
|
293
|
+
const [activeTab, setActiveTab] = useState(initialTabValue);
|
|
215
294
|
const prevActiveTabRef = useRef(activeTab);
|
|
216
295
|
|
|
217
296
|
useEffect(() => {
|
|
@@ -228,10 +307,13 @@ export const useActiveTab = ({ initialTab, tabsId }: UseActiveTabProps) => {
|
|
|
228
307
|
});
|
|
229
308
|
}, [activeTab, setSearchParams, tabsId]);
|
|
230
309
|
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
310
|
+
return useMemo(
|
|
311
|
+
() => ({
|
|
312
|
+
activeTab,
|
|
313
|
+
setActiveTab,
|
|
314
|
+
}),
|
|
315
|
+
[activeTab],
|
|
316
|
+
);
|
|
235
317
|
};
|
|
236
318
|
|
|
237
319
|
type GetInitialTabProps = {
|
|
@@ -37,20 +37,22 @@ export const useTelemetryFallback = () => ({
|
|
|
37
37
|
sendLanguagePickerLocaleChangedMessage: () => {},
|
|
38
38
|
sendSearchOpenedMessage: () => {},
|
|
39
39
|
sendSearchQueryMessage: () => {},
|
|
40
|
+
sendSearchAiOpenedMessage: () => {},
|
|
40
41
|
sendSearchAIQueryMessage: () => {},
|
|
42
|
+
sendSearchAIFeedbackMessage: () => {},
|
|
41
43
|
sendFeedbackMessage: () => {},
|
|
42
44
|
sendSearchResultClickedMessage: () => {},
|
|
43
45
|
sendRedirectMessage: () => {},
|
|
44
46
|
sendOpenapiDocsMessage: () => {},
|
|
45
47
|
sendCopyCodeSnippetClickedMessage: () => {},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
sendViewedMessage: () => {},
|
|
49
|
+
sendPerformanceMetricsMessage: () => {},
|
|
50
|
+
sendDownloadDefinitionClickedMessage: () => {},
|
|
51
|
+
sendSelectLanguageClickedMessage: () => {},
|
|
52
|
+
sendExpandCollapseAllClickedMessage: () => {},
|
|
53
|
+
sendSwitchServersClickedMessage: () => {},
|
|
54
|
+
sendExamplesSwitcherClickedMessage: () => {},
|
|
55
|
+
sendTryItOpenedMessage: () => {},
|
|
54
56
|
sendAsyncapiDocsViewedMessage: () => {},
|
|
55
57
|
sendAsyncapiDocsPerformanceMetricsMessage: () => {},
|
|
56
58
|
sendAsyncapiDocsSwitchMessageClickedMessage: () => {},
|
|
@@ -8,6 +8,7 @@ export type { UserClaims } from '../types/user-claims';
|
|
|
8
8
|
export type { OperationParameter, ParameterHighlight } from '../types/search';
|
|
9
9
|
export type { TFunction, TOptions } from '../types/l10n';
|
|
10
10
|
export type { SelectOption, SelectProps } from '../types/select';
|
|
11
|
+
export type { SegmentedOption, SegmentedProps } from '../types/segmented';
|
|
11
12
|
export { IS_BROWSER } from '../utils/dom';
|
|
12
13
|
export {
|
|
13
14
|
addLeadingSlash,
|
package/src/core/styles/dark.ts
CHANGED
|
@@ -36,6 +36,10 @@ const replayDarkMode = css`
|
|
|
36
36
|
--replay-server-variable-bg-color-hover: rgba(31, 10, 144, 0.4); // @presenter Color
|
|
37
37
|
--replay-path-parameter-bg-color-hover: rgba(5, 88, 99, 0.4); // @presenter Color
|
|
38
38
|
|
|
39
|
+
--replay-runtime-expression-color: rgba(147, 166, 249, 1); // @presenter Color
|
|
40
|
+
--replay-runtime-expression-bg-color: rgba(147, 166, 249, 0.16); // @presenter Color
|
|
41
|
+
--replay-operators-color: rgba(168, 143, 88, 1); // @presenter Color
|
|
42
|
+
|
|
39
43
|
// @tokens End
|
|
40
44
|
`;
|
|
41
45
|
|
|
@@ -19,7 +19,7 @@ import { checkbox } from '@redocly/theme/icons/CheckboxIcon/variables';
|
|
|
19
19
|
import { admonition } from '@redocly/theme/components/Admonition/variables';
|
|
20
20
|
import { footer } from '@redocly/theme/components/Footer/variables';
|
|
21
21
|
import { button } from '@redocly/theme/components/Button/variables';
|
|
22
|
-
import { aiAssistantButton } from '@redocly/theme/components/Buttons/variables';
|
|
22
|
+
import { aiAssistantButton, connectMCPButton } from '@redocly/theme/components/Buttons/variables';
|
|
23
23
|
import { navbar } from '@redocly/theme/components/Navbar/variables';
|
|
24
24
|
import { search } from '@redocly/theme/components/Search/variables';
|
|
25
25
|
import { menu, mobileMenu } from '@redocly/theme/components/Menu/variables';
|
|
@@ -1224,6 +1224,10 @@ const replay = css`
|
|
|
1224
1224
|
--replay-server-variable-bg-color-hover: rgba(119, 45, 240, 0.16); // @presenter Color
|
|
1225
1225
|
--replay-path-parameter-bg-color-hover: rgba(4, 117, 161, 0.16); // @presenter Color
|
|
1226
1226
|
|
|
1227
|
+
--replay-runtime-expression-color: rgba(54, 90, 249, 1); // @presenter Color
|
|
1228
|
+
--replay-runtime-expression-bg-color: rgba(54, 90, 249, 0.08); // @presenter Color
|
|
1229
|
+
--replay-operators-color: rgba(193, 142, 31, 1); // @presenter Color
|
|
1230
|
+
|
|
1227
1231
|
// @tokens End
|
|
1228
1232
|
`;
|
|
1229
1233
|
|
|
@@ -1239,6 +1243,7 @@ export const styles = css`
|
|
|
1239
1243
|
${breadcrumbs}
|
|
1240
1244
|
${button}
|
|
1241
1245
|
${aiAssistantButton}
|
|
1246
|
+
${connectMCPButton}
|
|
1242
1247
|
${cards}
|
|
1243
1248
|
${catalog}
|
|
1244
1249
|
${catalogClassic}
|
package/src/core/types/hooks.ts
CHANGED
|
@@ -86,6 +86,7 @@ export type ThemeHooks = {
|
|
|
86
86
|
useSearch: (
|
|
87
87
|
product?: string,
|
|
88
88
|
autoSearchDisabled?: boolean,
|
|
89
|
+
searchSessionId?: string,
|
|
89
90
|
) => {
|
|
90
91
|
query: string;
|
|
91
92
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
|
@@ -107,7 +108,10 @@ export type ThemeHooks = {
|
|
|
107
108
|
advancedSearch?: boolean;
|
|
108
109
|
askAi?: boolean;
|
|
109
110
|
};
|
|
110
|
-
useAiSearch: (
|
|
111
|
+
useAiSearch: (
|
|
112
|
+
options?: { filter?: SearchFilterItem[] },
|
|
113
|
+
searchSessionId?: string,
|
|
114
|
+
) => {
|
|
111
115
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
112
116
|
isGeneratingResponse: boolean;
|
|
113
117
|
question: string;
|
package/src/core/types/index.ts
CHANGED
package/src/core/types/l10n.ts
CHANGED
|
@@ -86,6 +86,7 @@ export type TranslationKey =
|
|
|
86
86
|
| 'search.ai.welcomeText'
|
|
87
87
|
| 'search.ai.newConversation'
|
|
88
88
|
| 'search.ai.backToSearch'
|
|
89
|
+
| 'search.ai.back'
|
|
89
90
|
| 'search.ai.placeholder'
|
|
90
91
|
| 'search.ai.generatingResponse'
|
|
91
92
|
| 'search.ai.followUpQuestion'
|
|
@@ -94,6 +95,9 @@ export type TranslationKey =
|
|
|
94
95
|
| 'search.ai.resourcesFound'
|
|
95
96
|
| 'search.ai.resourcesFound.basedOn'
|
|
96
97
|
| 'search.ai.resourcesFound.resources'
|
|
98
|
+
| 'search.ai.feedback.title'
|
|
99
|
+
| 'search.ai.feedback.detailsPlaceholder'
|
|
100
|
+
| 'search.ai.feedback.thanks'
|
|
97
101
|
| 'search.ai.button'
|
|
98
102
|
| 'search.ai.label'
|
|
99
103
|
| 'search.ai.disclaimer'
|
|
@@ -211,6 +215,13 @@ export type TranslationKey =
|
|
|
211
215
|
| 'page.actions.cursorMcpButtonText'
|
|
212
216
|
| 'page.actions.cursorMcpTitle'
|
|
213
217
|
| 'page.actions.cursorMcpDescription'
|
|
218
|
+
| 'page.actions.connectMcp'
|
|
219
|
+
| 'page.actions.connectMcp.cursor'
|
|
220
|
+
| 'page.actions.connectMcp.cursorDescription'
|
|
221
|
+
| 'page.actions.connectMcp.vscode'
|
|
222
|
+
| 'page.actions.connectMcp.vscodeDescription'
|
|
223
|
+
| 'page.actions.connectMcp.copyConfig'
|
|
224
|
+
| 'page.actions.connectMcp.copyConfigDescription'
|
|
214
225
|
| 'openapi.download.description.title'
|
|
215
226
|
| 'openapi.info.title'
|
|
216
227
|
| 'openapi.info.contact.url'
|
|
@@ -267,6 +278,8 @@ export type TranslationKey =
|
|
|
267
278
|
| 'openapi.noResponseExample'
|
|
268
279
|
| 'openapi.discriminator.searchPlaceholder'
|
|
269
280
|
| 'openapi.discriminator.searchNoResults'
|
|
281
|
+
| 'openapi.discriminator.defaultMapping'
|
|
282
|
+
| 'openapi.discriminator.defaultMappingTooltip'
|
|
270
283
|
| 'openapi.noResponseContent'
|
|
271
284
|
| 'openapi.noRequestPayload'
|
|
272
285
|
| 'openapi.hidePattern'
|
package/src/core/types/search.ts
CHANGED
|
@@ -93,11 +93,20 @@ export type AiSearchErrorConfig = {
|
|
|
93
93
|
messageDefault: string;
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
export enum FeedbackType {
|
|
97
|
+
Like = 'like',
|
|
98
|
+
Dislike = 'dislike',
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type SearchAiMessageResource = {
|
|
102
|
+
url: string;
|
|
103
|
+
title: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
96
106
|
export type AiSearchConversationItem = {
|
|
97
107
|
role: AiSearchConversationRole;
|
|
98
108
|
content: string;
|
|
99
|
-
resources?:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}[];
|
|
109
|
+
resources?: SearchAiMessageResource[];
|
|
110
|
+
messageId?: string;
|
|
111
|
+
feedback?: FeedbackType;
|
|
103
112
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { SelectOption } from './select';
|
|
3
|
+
|
|
4
|
+
export type SegmentedOption<T> = SelectOption<T> & {
|
|
5
|
+
divider?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type SegmentedProps<T = any> = {
|
|
9
|
+
options: SegmentedOption<T>[];
|
|
10
|
+
value: T;
|
|
11
|
+
onChange: ({ label, value }: SegmentedOption<T>) => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
size?: 'regular' | 'small';
|
|
14
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility function for use in React frontmatter
|
|
3
|
+
* This function creates a string that will be processed by the frontmatter loader
|
|
4
|
+
* to convert it into a translation key object
|
|
5
|
+
*/
|
|
6
|
+
export const frontmatterTranslate = (key: string, defaultValue: string): string => {
|
|
7
|
+
const escapedDefaultValue = defaultValue.replace(/'/g, "\\'");
|
|
8
|
+
return `frontmatterTranslate('${key}', '${escapedDefaultValue}')`;
|
|
9
|
+
};
|
package/src/core/utils/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './clipboard-service';
|
|
2
2
|
export * from './css-variables';
|
|
3
3
|
export * from './add-line-numbers';
|
|
4
|
+
export * from './mcp';
|
|
4
5
|
export * from './media-css';
|
|
5
6
|
export * from './theme-helpers';
|
|
6
7
|
export * from './class-names';
|
|
@@ -38,3 +39,4 @@ export * from './enhanced-smoothstep';
|
|
|
38
39
|
export * from './icon-resolver';
|
|
39
40
|
export * from './dynamic';
|
|
40
41
|
export * from './tabs';
|
|
42
|
+
export * from './frontmatter-translate';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MCPClientType, McpConnectionParams } from '../types';
|
|
2
|
+
|
|
3
|
+
function generateCursorMCPDeepLink(config: McpConnectionParams): string {
|
|
4
|
+
const cursorConfig = {
|
|
5
|
+
url: config.url,
|
|
6
|
+
description: 'MCP Server',
|
|
7
|
+
};
|
|
8
|
+
const encodedConfig = btoa(JSON.stringify(cursorConfig));
|
|
9
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${config.serverName}&config=${encodedConfig}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function generateVSCodeMCPDeepLink(config: McpConnectionParams): string {
|
|
13
|
+
const vscodeConfig = {
|
|
14
|
+
name: config.serverName,
|
|
15
|
+
url: config.url,
|
|
16
|
+
type: 'http',
|
|
17
|
+
};
|
|
18
|
+
const encodedConfig = encodeURIComponent(JSON.stringify(vscodeConfig));
|
|
19
|
+
return `vscode:mcp/install?${encodedConfig}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function generateMCPDeepLink(
|
|
23
|
+
clientType: MCPClientType,
|
|
24
|
+
config: McpConnectionParams,
|
|
25
|
+
): string {
|
|
26
|
+
switch (clientType) {
|
|
27
|
+
case 'cursor':
|
|
28
|
+
return generateCursorMCPDeepLink(config);
|
|
29
|
+
case 'vscode':
|
|
30
|
+
return generateVSCodeMCPDeepLink(config);
|
|
31
|
+
default:
|
|
32
|
+
return generateCursorMCPDeepLink(config);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { IconProps } from '@redocly/theme/icons/types';
|
|
@@ -15,9 +15,18 @@ export interface AiStarsGradientIconProps extends IconProps {
|
|
|
15
15
|
const Icon = (props: AiStarsGradientIconProps) => {
|
|
16
16
|
const { color = '', background, borderRadius, padding, margin, ...restProps } = props;
|
|
17
17
|
|
|
18
|
-
const resolvedColor = color
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const [resolvedColor, setResolvedColor] = useState(color);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const resolvedColor = color.startsWith('var(')
|
|
22
|
+
? window
|
|
23
|
+
.getComputedStyle(document.documentElement)
|
|
24
|
+
.getPropertyValue(color.slice(4, -1))
|
|
25
|
+
.trim()
|
|
26
|
+
: color;
|
|
27
|
+
|
|
28
|
+
setResolvedColor(resolvedColor);
|
|
29
|
+
}, [color]);
|
|
21
30
|
|
|
22
31
|
const isColorOverridden = resolvedColor && resolvedColor !== 'none';
|
|
23
32
|
const fill = isColorOverridden ? resolvedColor : 'url(#gradient)';
|