@redocly/theme 0.58.0-next.9 → 0.59.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Catalog/CatalogEntity/CatalogEntity.d.ts +5 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +4 -4
- package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +3 -3
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.d.ts +5 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +9 -7
- package/lib/components/CodeBlock/CodeBlock.d.ts +5 -12
- package/lib/components/CodeBlock/CodeBlockControls.d.ts +3 -3
- package/lib/components/CodeBlock/CodeBlockControls.js +1 -1
- package/lib/components/CodeBlock/CodeBlockDropdown.d.ts +2 -2
- package/lib/components/CodeBlock/CodeBlockDropdown.js +4 -13
- package/lib/components/CodeBlock/CodeBlockTabs.d.ts +2 -2
- package/lib/components/CodeBlock/CodeBlockTabs.js +4 -3
- package/lib/components/JsonViewer/JsonViewer.d.ts +1 -1
- package/lib/components/JsonViewer/JsonViewer.js +9 -10
- package/lib/components/PageActions/PageActions.d.ts +4 -1
- package/lib/components/PageActions/PageActions.js +2 -2
- package/lib/components/Panel/variables.js +1 -0
- package/lib/components/Tag/Tag.d.ts +3 -2
- package/lib/components/Tag/Tag.js +21 -5
- package/lib/components/Tag/variables.dark.js +135 -0
- package/lib/components/Tag/variables.js +120 -58
- package/lib/core/constants/catalog.js +4 -0
- package/lib/core/contexts/CodeSnippetContext.d.ts +14 -6
- package/lib/core/contexts/CodeSnippetContext.js +57 -14
- package/lib/core/hooks/use-codeblock-tabs-controls.d.ts +2 -2
- package/lib/core/hooks/use-local-state.js +22 -18
- package/lib/core/hooks/use-page-actions.d.ts +2 -1
- package/lib/core/hooks/use-page-actions.js +48 -6
- package/lib/core/hooks/use-tabs.d.ts +11 -6
- package/lib/core/hooks/use-tabs.js +117 -207
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/openapi/index.js +3 -1
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/open-api-server.d.ts +1 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/tabs.d.ts +1 -0
- package/lib/core/utils/tabs.js +8 -0
- package/lib/icons/CursorIcon/CursorIcon.d.ts +9 -0
- package/lib/icons/CursorIcon/CursorIcon.js +22 -0
- package/lib/layouts/DocumentationLayout.js +1 -3
- package/lib/markdoc/components/CodeGroup/CodeGroup.js +49 -27
- package/lib/markdoc/components/Tabs/Tab.js +1 -1
- package/lib/markdoc/components/Tabs/TabList.d.ts +2 -14
- package/lib/markdoc/components/Tabs/TabList.js +65 -16
- package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -2
- package/lib/markdoc/components/Tabs/Tabs.js +11 -87
- package/lib/markdoc/tags/tabs.js +5 -0
- package/package.json +4 -4
- package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +15 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +3 -3
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +27 -18
- package/src/components/CodeBlock/CodeBlock.tsx +5 -11
- package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
- package/src/components/CodeBlock/CodeBlockDropdown.tsx +11 -20
- package/src/components/CodeBlock/CodeBlockTabs.tsx +8 -8
- package/src/components/JsonViewer/JsonViewer.tsx +16 -9
- package/src/components/PageActions/PageActions.tsx +6 -4
- package/src/components/Panel/variables.ts +1 -0
- package/src/components/Tag/Tag.tsx +33 -8
- package/src/components/Tag/variables.dark.ts +135 -0
- package/src/components/Tag/variables.ts +120 -58
- package/src/core/constants/catalog.ts +4 -0
- package/src/core/contexts/CodeSnippetContext.tsx +54 -18
- package/src/core/hooks/use-codeblock-tabs-controls.ts +2 -2
- package/src/core/hooks/use-local-state.ts +28 -19
- package/src/core/hooks/use-page-actions.ts +63 -6
- package/src/core/hooks/use-tabs.ts +160 -238
- package/src/core/openapi/index.ts +1 -0
- package/src/core/types/l10n.ts +13 -0
- package/src/core/types/open-api-server.ts +1 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/tabs.ts +4 -0
- package/src/icons/CursorIcon/CursorIcon.tsx +35 -0
- package/src/layouts/DocumentationLayout.tsx +3 -10
- package/src/markdoc/components/CodeGroup/CodeGroup.tsx +81 -52
- package/src/markdoc/components/Tabs/Tab.tsx +1 -0
- package/src/markdoc/components/Tabs/TabList.tsx +85 -30
- package/src/markdoc/components/Tabs/Tabs.tsx +12 -125
- package/src/markdoc/tags/tabs.ts +5 -0
|
@@ -1,115 +1,59 @@
|
|
|
1
|
-
import { useCallback, useRef, useState, useEffect } from 'react';
|
|
1
|
+
import { useCallback, useRef, useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useSearchParams } from 'react-router-dom';
|
|
2
3
|
|
|
3
4
|
type UseTabsProps = {
|
|
4
|
-
|
|
5
|
+
activeTab: string;
|
|
6
|
+
onTabChange: (tab: string) => void;
|
|
5
7
|
totalTabs: number;
|
|
6
8
|
containerRef?: React.RefObject<HTMLElement | null>;
|
|
7
9
|
};
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
type Tabs = {
|
|
12
|
+
visible: number[];
|
|
13
|
+
overflow: number[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const MORE_BUTTON_WIDTH = 80;
|
|
17
|
+
const TABS_GAP = 8;
|
|
18
|
+
|
|
19
|
+
export function useTabs({ activeTab, onTabChange, totalTabs, containerRef }: UseTabsProps) {
|
|
20
|
+
const [tabs, setTabs] = useState<Tabs>({
|
|
21
|
+
visible: Array.from({ length: totalTabs }, (_, i) => i),
|
|
22
|
+
overflow: [],
|
|
23
|
+
});
|
|
24
|
+
|
|
16
25
|
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
26
|
+
const tabWidthsRef = useRef<number[]>([]);
|
|
17
27
|
const tabLabelsRef = useRef<string[]>([]);
|
|
18
|
-
const resizeTimeoutRef = useRef<number | undefined>(undefined);
|
|
19
|
-
const [ready, setReady] = useState<boolean>(false);
|
|
20
|
-
const hasCalculatedOnce = useRef(false);
|
|
21
|
-
const lastWidthRef = useRef<number>(0);
|
|
22
|
-
const originalOrderRef = useRef<number[]>([]);
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
originalOrderRef.current = Array.from({ length: totalTabs }, (_, i) => i);
|
|
26
|
-
}, [totalTabs]);
|
|
29
|
+
const allTabsHidden = useMemo(() => tabs.visible.length === 0, [tabs.visible]);
|
|
27
30
|
|
|
28
31
|
const setTabRef = useCallback((element: HTMLButtonElement | null, index: number) => {
|
|
29
32
|
tabRefs.current[index] = element;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
33
|
+
|
|
34
|
+
const width = element?.offsetWidth;
|
|
35
|
+
if (width) {
|
|
36
|
+
tabWidthsRef.current[index] = width;
|
|
35
37
|
}
|
|
36
|
-
}, []);
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const label = element?.getAttribute('data-label');
|
|
40
|
+
if (label) {
|
|
41
|
+
tabLabelsRef.current[index] = label;
|
|
42
|
+
}
|
|
41
43
|
}, []);
|
|
42
44
|
|
|
43
45
|
const focusTab = (index: number) => {
|
|
44
46
|
const currentElement = tabRefs.current[index];
|
|
45
|
-
|
|
46
|
-
currentElement.focus();
|
|
47
|
-
}
|
|
47
|
+
currentElement?.focus();
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
const onTabSelect = useCallback(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const onTabClick = useCallback(
|
|
57
|
-
(labelOrIndex: string | number) => {
|
|
58
|
-
let clickedIndex: number;
|
|
59
|
-
|
|
60
|
-
if (typeof labelOrIndex === 'string') {
|
|
61
|
-
clickedIndex = tabRefs.current.findIndex(
|
|
62
|
-
(ref) => ref?.getAttribute('data-label') === labelOrIndex,
|
|
63
|
-
);
|
|
64
|
-
if (clickedIndex === -1) return;
|
|
65
|
-
} else {
|
|
66
|
-
clickedIndex = labelOrIndex;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (allTabsHidden) {
|
|
70
|
-
const label = tabLabelsRef.current[clickedIndex];
|
|
71
|
-
if (label) {
|
|
72
|
-
setActiveTab(label);
|
|
73
|
-
focusTab(clickedIndex);
|
|
74
|
-
}
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (overflowTabs.includes(clickedIndex)) {
|
|
79
|
-
const newVisibleTabs = [...visibleTabs];
|
|
80
|
-
const newOverflowTabs = [...overflowTabs];
|
|
81
|
-
|
|
82
|
-
const clickedIdxInOverflow = newOverflowTabs.indexOf(clickedIndex);
|
|
83
|
-
if (clickedIdxInOverflow !== -1) {
|
|
84
|
-
newOverflowTabs.splice(clickedIdxInOverflow, 1);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const lastVisible = newVisibleTabs.pop();
|
|
88
|
-
if (lastVisible !== undefined) {
|
|
89
|
-
newOverflowTabs.unshift(lastVisible);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
newVisibleTabs.push(clickedIndex);
|
|
93
|
-
|
|
94
|
-
setVisibleTabs(newVisibleTabs);
|
|
95
|
-
setOverflowTabs(newOverflowTabs);
|
|
96
|
-
|
|
97
|
-
requestAnimationFrame(() => {
|
|
98
|
-
const label = tabRefs.current[clickedIndex]?.getAttribute('data-label');
|
|
99
|
-
if (label) {
|
|
100
|
-
setActiveTab(label);
|
|
101
|
-
focusTab(clickedIndex);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
} else {
|
|
105
|
-
const label = tabRefs.current[clickedIndex]?.getAttribute('data-label');
|
|
106
|
-
if (label) {
|
|
107
|
-
setActiveTab(label);
|
|
108
|
-
focusTab(clickedIndex);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
50
|
+
const onTabSelect = useCallback(
|
|
51
|
+
(index: number) => {
|
|
52
|
+
focusTab(index);
|
|
53
|
+
const label = tabRefs.current[index]?.getAttribute('data-label');
|
|
54
|
+
if (label) onTabChange(label);
|
|
111
55
|
},
|
|
112
|
-
[
|
|
56
|
+
[onTabChange],
|
|
113
57
|
);
|
|
114
58
|
|
|
115
59
|
const handleKeyboard = useCallback(
|
|
@@ -133,196 +77,174 @@ export function useTabs({ initialTab, totalTabs, containerRef }: UseTabsProps) {
|
|
|
133
77
|
[totalTabs, onTabSelect],
|
|
134
78
|
);
|
|
135
79
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
80
|
+
const replaceLastVisibleTabWithClickedOverflowTab = useCallback(
|
|
81
|
+
(clickedIndex: number) => {
|
|
82
|
+
const { visible: visibleTabs, overflow: overflowTabs } = tabs;
|
|
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
|
+
}
|
|
139
96
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
97
|
+
setTabs({
|
|
98
|
+
visible: newVisibleTabs,
|
|
99
|
+
overflow: newOverflowTabs,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
[tabs],
|
|
103
|
+
);
|
|
147
104
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
105
|
+
const onTabClick = useCallback(
|
|
106
|
+
(labelOrIndex: string | number) => {
|
|
107
|
+
const clickedIndex =
|
|
108
|
+
typeof labelOrIndex === 'string'
|
|
109
|
+
? tabRefs.current.findIndex((ref) => ref?.getAttribute('data-label') === labelOrIndex)
|
|
110
|
+
: labelOrIndex;
|
|
111
|
+
|
|
112
|
+
if (clickedIndex === -1) return;
|
|
113
|
+
|
|
114
|
+
const hasOverflowTabs = tabs.overflow.length > 0;
|
|
115
|
+
if (hasOverflowTabs && !allTabsHidden && tabs.overflow.includes(clickedIndex)) {
|
|
116
|
+
replaceLastVisibleTabWithClickedOverflowTab(clickedIndex);
|
|
117
|
+
}
|
|
152
118
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
119
|
+
const label = tabLabelsRef.current[clickedIndex];
|
|
120
|
+
if (label) {
|
|
121
|
+
onTabChange(label);
|
|
122
|
+
focusTab(clickedIndex);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
[allTabsHidden, tabs.overflow, onTabChange, replaceLastVisibleTabWithClickedOverflowTab],
|
|
126
|
+
);
|
|
156
127
|
|
|
157
|
-
|
|
158
|
-
const
|
|
128
|
+
const calculateVisibleTabs = useCallback(() => {
|
|
129
|
+
const container = containerRef?.current;
|
|
130
|
+
if (!container) return;
|
|
159
131
|
|
|
132
|
+
const containerWidth = container.offsetWidth;
|
|
133
|
+
const tabWidths = tabWidthsRef.current;
|
|
160
134
|
const activeTabIndex = tabRefs.current.findIndex(
|
|
161
135
|
(ref) => ref?.getAttribute('data-label') === activeTab,
|
|
162
136
|
);
|
|
163
137
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
138
|
+
// Active tab should always be visible, so we include it at the beginning of the array
|
|
139
|
+
let tabsWidth = activeTabIndex !== -1 ? tabWidths[activeTabIndex] : 0;
|
|
140
|
+
const visible = activeTabIndex !== -1 ? [activeTabIndex] : [];
|
|
141
|
+
const overflow = [];
|
|
167
142
|
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
if (i
|
|
171
|
-
|
|
143
|
+
for (let i = 0; i < tabWidths.length; i++) {
|
|
144
|
+
// Skip active tab, it was added initially
|
|
145
|
+
if (i === activeTabIndex) {
|
|
146
|
+
continue;
|
|
172
147
|
}
|
|
173
|
-
});
|
|
174
148
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
setOverflowTabs(Array.from({ length: totalTabs }, (_, i) => i));
|
|
178
|
-
setAllTabsHidden(true);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
149
|
+
const tabWidthWithGap = tabWidths[i] + TABS_GAP;
|
|
150
|
+
const projectedWidth = tabsWidth + tabWidthWithGap;
|
|
181
151
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
152
|
+
if (projectedWidth <= containerWidth) {
|
|
153
|
+
visible.push(i);
|
|
154
|
+
tabsWidth += tabWidthWithGap;
|
|
155
|
+
} else {
|
|
156
|
+
overflow.push(i);
|
|
187
157
|
}
|
|
188
|
-
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
tabsByType.forEach((tabIndices) => {
|
|
192
|
-
let typeCurrentWidth = currentWidth;
|
|
193
|
-
const typeVisible: number[] = [];
|
|
194
|
-
const typeOverflow: number[] = [];
|
|
195
|
-
|
|
196
|
-
tabIndices.slice(0, minVisibleTabs).forEach((tabIndex) => {
|
|
197
|
-
const tabWidth = tabWidths[tabIndex];
|
|
198
|
-
const projectedWidth =
|
|
199
|
-
typeCurrentWidth +
|
|
200
|
-
tabWidth +
|
|
201
|
-
(typeVisible.length > 0 ? moreButtonWidth + safetyMargin : 0);
|
|
202
|
-
|
|
203
|
-
if (projectedWidth <= containerWidth) {
|
|
204
|
-
typeVisible.push(tabIndex);
|
|
205
|
-
typeCurrentWidth += tabWidth;
|
|
206
|
-
} else {
|
|
207
|
-
typeOverflow.push(tabIndex);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
tabIndices.slice(minVisibleTabs).forEach((tabIndex) => {
|
|
212
|
-
const tabWidth = tabWidths[tabIndex];
|
|
213
|
-
const projectedWidth = typeCurrentWidth + tabWidth + moreButtonWidth + safetyMargin;
|
|
214
|
-
|
|
215
|
-
if (projectedWidth <= containerWidth) {
|
|
216
|
-
typeVisible.push(tabIndex);
|
|
217
|
-
typeCurrentWidth += tabWidth;
|
|
218
|
-
} else {
|
|
219
|
-
typeOverflow.push(tabIndex);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
158
|
+
}
|
|
222
159
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
currentWidth = typeCurrentWidth;
|
|
226
|
-
});
|
|
160
|
+
if (overflow.length > 0) {
|
|
161
|
+
tabsWidth += MORE_BUTTON_WIDTH;
|
|
227
162
|
|
|
228
|
-
|
|
229
|
-
|
|
163
|
+
// Remove tabs starting from the end of the array until the width of the visible tabs is less than the container width
|
|
164
|
+
while (tabsWidth > containerWidth && visible.length) {
|
|
230
165
|
const removed = visible.pop();
|
|
231
166
|
if (removed !== undefined) {
|
|
232
167
|
overflow.unshift(removed);
|
|
168
|
+
tabsWidth -= tabWidths[removed];
|
|
233
169
|
}
|
|
234
170
|
}
|
|
235
|
-
|
|
236
|
-
visible.push(activeTabIndex);
|
|
237
|
-
const activeOverflowIndex = overflow.indexOf(activeTabIndex);
|
|
238
|
-
if (activeOverflowIndex !== -1) overflow.splice(activeOverflowIndex, 1);
|
|
239
171
|
}
|
|
240
172
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}, [containerRef,
|
|
173
|
+
setTabs({
|
|
174
|
+
visible,
|
|
175
|
+
overflow,
|
|
176
|
+
});
|
|
177
|
+
}, [containerRef, activeTab]);
|
|
246
178
|
|
|
247
179
|
useEffect(() => {
|
|
248
180
|
if (!containerRef?.current) return;
|
|
249
181
|
|
|
250
|
-
|
|
251
|
-
const allTabsReady =
|
|
252
|
-
tabRefs.current.length === totalTabs && tabRefs.current.every((tab) => tab?.offsetWidth);
|
|
253
|
-
|
|
254
|
-
if (!allTabsReady) {
|
|
255
|
-
resizeTimeoutRef.current = requestAnimationFrame(ensureTabsReady);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
calculateVisibleTabs();
|
|
260
|
-
hasCalculatedOnce.current = true;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
resizeTimeoutRef.current = requestAnimationFrame(ensureTabsReady);
|
|
264
|
-
|
|
265
|
-
let resizeTimeout: number;
|
|
182
|
+
let resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
|
|
266
183
|
const handleResize = () => {
|
|
267
|
-
if (!hasCalculatedOnce.current) return;
|
|
268
|
-
|
|
269
184
|
if (resizeTimeout) {
|
|
270
185
|
cancelAnimationFrame(resizeTimeout);
|
|
271
186
|
}
|
|
272
|
-
|
|
273
|
-
resizeTimeout = requestAnimationFrame(() => {
|
|
274
|
-
if (resizeTimeoutRef.current) {
|
|
275
|
-
cancelAnimationFrame(resizeTimeoutRef.current);
|
|
276
|
-
}
|
|
277
|
-
resizeTimeoutRef.current = requestAnimationFrame(() => {
|
|
278
|
-
const container = containerRef?.current;
|
|
279
|
-
if (!container) return;
|
|
280
|
-
|
|
281
|
-
const currentWidth = container.offsetWidth;
|
|
282
|
-
|
|
283
|
-
if (Math.abs(lastWidthRef.current - currentWidth) > 5) {
|
|
284
|
-
lastWidthRef.current = currentWidth;
|
|
285
|
-
calculateVisibleTabs();
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
});
|
|
187
|
+
resizeTimeout = requestAnimationFrame(calculateVisibleTabs);
|
|
289
188
|
};
|
|
290
189
|
|
|
291
|
-
const resizeObserver = new ResizeObserver(handleResize);
|
|
292
|
-
resizeObserver.observe(containerRef.current);
|
|
293
190
|
window.addEventListener('resize', handleResize);
|
|
294
|
-
|
|
295
191
|
return () => {
|
|
296
|
-
resizeObserver.disconnect();
|
|
297
192
|
window.removeEventListener('resize', handleResize);
|
|
298
|
-
|
|
299
|
-
cancelAnimationFrame(resizeTimeoutRef.current);
|
|
300
|
-
}
|
|
301
|
-
if (resizeTimeout) {
|
|
302
|
-
cancelAnimationFrame(resizeTimeout);
|
|
303
|
-
}
|
|
193
|
+
cancelAnimationFrame(resizeTimeout);
|
|
304
194
|
};
|
|
305
195
|
}, [containerRef, totalTabs, calculateVisibleTabs]);
|
|
306
196
|
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
const raf = requestAnimationFrame(() => {
|
|
309
|
-
setReady(true);
|
|
310
|
-
calculateVisibleTabs();
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
return () => cancelAnimationFrame(raf);
|
|
314
|
-
}, [calculateVisibleTabs]);
|
|
315
|
-
|
|
316
197
|
return {
|
|
317
|
-
activeTab,
|
|
318
|
-
setActiveTab,
|
|
319
198
|
setTabRef,
|
|
320
199
|
onTabClick,
|
|
321
200
|
handleKeyboard,
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
overflowTabs,
|
|
325
|
-
ready,
|
|
201
|
+
visibleTabs: tabs.visible,
|
|
202
|
+
overflowTabs: tabs.overflow,
|
|
326
203
|
allTabsHidden,
|
|
327
204
|
};
|
|
328
205
|
}
|
|
206
|
+
|
|
207
|
+
type UseActiveTabProps = {
|
|
208
|
+
initialTab: string;
|
|
209
|
+
tabsId?: string;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const useActiveTab = ({ initialTab, tabsId }: UseActiveTabProps) => {
|
|
213
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
214
|
+
const [activeTab, setActiveTab] = useState(getInitialTab({ initialTab, searchParams, tabsId }));
|
|
215
|
+
const prevActiveTabRef = useRef(activeTab);
|
|
216
|
+
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const hasActiveTabChanged = prevActiveTabRef.current !== activeTab;
|
|
219
|
+
if (!tabsId || !hasActiveTabChanged) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
prevActiveTabRef.current = activeTab;
|
|
224
|
+
|
|
225
|
+
setSearchParams((searchParams) => {
|
|
226
|
+
searchParams.set(tabsId, activeTab);
|
|
227
|
+
return searchParams;
|
|
228
|
+
});
|
|
229
|
+
}, [activeTab, setSearchParams, tabsId]);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
activeTab,
|
|
233
|
+
setActiveTab,
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
type GetInitialTabProps = {
|
|
238
|
+
initialTab: string;
|
|
239
|
+
searchParams: URLSearchParams;
|
|
240
|
+
tabsId?: string;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const getInitialTab = ({ initialTab, searchParams, tabsId }: GetInitialTabProps) => {
|
|
244
|
+
let resultTab = initialTab;
|
|
245
|
+
if (tabsId) {
|
|
246
|
+
const tabFromUrl = searchParams.get(tabsId);
|
|
247
|
+
resultTab = tabFromUrl ? tabFromUrl : resultTab;
|
|
248
|
+
}
|
|
249
|
+
return resultTab;
|
|
250
|
+
};
|
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
addTrailingSlash,
|
|
18
18
|
withPathPrefix,
|
|
19
19
|
} from '../utils/urls';
|
|
20
|
+
export { capitalize } from '../utils/string';
|
|
20
21
|
export { typedMemo } from '../hoc/typedMemo';
|
|
21
22
|
export { useMount } from '../hooks/use-mount';
|
|
22
23
|
export { GlobalStyle } from '../styles/global';
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -206,6 +206,9 @@ export type TranslationKey =
|
|
|
206
206
|
| 'page.actions.claudeTitle'
|
|
207
207
|
| 'page.actions.claudeButtonText'
|
|
208
208
|
| 'page.actions.claudeDescription'
|
|
209
|
+
| 'page.actions.cursorMcpButtonText'
|
|
210
|
+
| 'page.actions.cursorMcpTitle'
|
|
211
|
+
| 'page.actions.cursorMcpDescription'
|
|
209
212
|
| 'openapi.download.description.title'
|
|
210
213
|
| 'openapi.info.title'
|
|
211
214
|
| 'openapi.info.contact.url'
|
|
@@ -283,6 +286,16 @@ export type TranslationKey =
|
|
|
283
286
|
| 'openapi.schemaCatalogLink.title'
|
|
284
287
|
| 'openapi.schemaCatalogLink.copyButtonTooltip'
|
|
285
288
|
| 'openapi.schemaCatalogLink.copiedTooltip'
|
|
289
|
+
| 'openapi.mcp.title'
|
|
290
|
+
| 'openapi.mcp.endpoint'
|
|
291
|
+
| 'openapi.mcp.tools'
|
|
292
|
+
| 'openapi.mcp.protocolVersion'
|
|
293
|
+
| 'openapi.mcp.capabilities'
|
|
294
|
+
| 'openapi.mcp.experimentalCapabilities'
|
|
295
|
+
| 'openapi.mcp.inputSchema'
|
|
296
|
+
| 'openapi.mcp.inputExample'
|
|
297
|
+
| 'openapi.mcp.outputSchema'
|
|
298
|
+
| 'openapi.mcp.outputExample'
|
|
286
299
|
| 'asyncapi.download.description.title'
|
|
287
300
|
| 'asyncapi.info.title'
|
|
288
301
|
| 'graphql.queries'
|
package/src/core/utils/index.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
const Icon = (props: IconProps) => (
|
|
7
|
+
<svg
|
|
8
|
+
width="16"
|
|
9
|
+
height="16"
|
|
10
|
+
viewBox="0 0 16 16"
|
|
11
|
+
fill="none"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path d="M7.99956 15V8.0001L2 11.4999L7.99956 15Z" fill="#939393" />
|
|
16
|
+
<path d="M14 4.49979L7.99956 15V8.0001L14 4.49979Z" fill="#E3E3E3" />
|
|
17
|
+
<path d="M2 4.49979H14L7.99956 8.0001L2 4.49979Z" fill="white" />
|
|
18
|
+
<path d="M8.00025 1V4.49995L14 4.49979L8.00025 1Z" fill="#444444" />
|
|
19
|
+
<path
|
|
20
|
+
d="M2 4.49979L8.00025 4.49995V1L2 4.49979ZM13.9999 11.4998L10.9999 9.74987L7.99956 15L13.9999 11.4998Z"
|
|
21
|
+
fill="#939393"
|
|
22
|
+
/>
|
|
23
|
+
<path
|
|
24
|
+
d="M14 4.49979L10.9999 9.74987L13.9999 11.4998L14 4.49979ZM7.99956 8.0001L2 11.4999V4.49979L7.99956 8.0001Z"
|
|
25
|
+
fill="#444444"
|
|
26
|
+
/>
|
|
27
|
+
</svg>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const CursorIcon = styled(Icon).attrs(() => ({
|
|
31
|
+
'data-component-name': 'icons/CursorIcon/CursorIcon',
|
|
32
|
+
}))<IconProps>`
|
|
33
|
+
height: ${({ size }) => size || '16px'};
|
|
34
|
+
width: ${({ size }) => size || '16px'};
|
|
35
|
+
`;
|
|
@@ -9,12 +9,7 @@ import { breakpoints } from '@redocly/theme/core/utils';
|
|
|
9
9
|
import { PageNavigation } from '@redocly/theme/components/PageNavigation/PageNavigation';
|
|
10
10
|
import { LastUpdated } from '@redocly/theme/components/LastUpdated/LastUpdated';
|
|
11
11
|
import { Breadcrumbs as ThemeBreadcrumbs } from '@redocly/theme/components/Breadcrumbs/Breadcrumbs';
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
CodeSnippetContext,
|
|
15
|
-
CODE_GROUP_SNIPPET_NAME_KEY,
|
|
16
|
-
} from '../core/contexts/CodeSnippetContext';
|
|
17
|
-
import { useLocalState } from '../core/hooks/use-local-state';
|
|
12
|
+
import { CodeSnippetProvider } from '@redocly/theme/core/contexts/CodeSnippetContext';
|
|
18
13
|
|
|
19
14
|
type DocumentationLayoutProps = {
|
|
20
15
|
tableOfContent: React.ReactNode;
|
|
@@ -44,10 +39,8 @@ export function DocumentationLayout({
|
|
|
44
39
|
const { editPage: themeEditPage } = config || {};
|
|
45
40
|
const mergedConf = editPage ? { ...themeEditPage, ...editPage } : undefined;
|
|
46
41
|
|
|
47
|
-
const [activeSnippetName, setActiveSnippetName] = useLocalState(CODE_GROUP_SNIPPET_NAME_KEY, '');
|
|
48
|
-
|
|
49
42
|
return (
|
|
50
|
-
<
|
|
43
|
+
<CodeSnippetProvider>
|
|
51
44
|
<LayoutWrapper data-component-name="Layout/DocumentationLayout" className={className}>
|
|
52
45
|
<ContentWrapper withToc={!config?.toc?.hide}>
|
|
53
46
|
<Breadcrumbs />
|
|
@@ -61,7 +54,7 @@ export function DocumentationLayout({
|
|
|
61
54
|
</ContentWrapper>
|
|
62
55
|
{tableOfContent}
|
|
63
56
|
</LayoutWrapper>
|
|
64
|
-
</
|
|
57
|
+
</CodeSnippetProvider>
|
|
65
58
|
);
|
|
66
59
|
}
|
|
67
60
|
|