@redocly/theme 0.58.1 → 0.59.0-next.1
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/Accordion/Accordion.d.ts +12 -0
- package/lib/components/Accordion/Accordion.js +75 -0
- package/lib/components/Accordion/AccordionBody.d.ts +8 -0
- package/lib/components/Accordion/AccordionBody.js +63 -0
- package/lib/components/Accordion/AccordionHeader.d.ts +10 -0
- package/lib/components/Accordion/AccordionHeader.js +37 -0
- package/lib/components/Accordion/AccordionTitle.d.ts +6 -0
- package/lib/components/Accordion/AccordionTitle.js +20 -0
- package/lib/components/Accordion/variables.d.ts +1 -0
- package/lib/components/Accordion/variables.js +59 -0
- package/lib/components/Buttons/AIAssistantButton.d.ts +2 -0
- package/lib/components/Buttons/AIAssistantButton.js +125 -0
- package/lib/components/Buttons/variables.d.ts +1 -0
- package/lib/components/Buttons/variables.dark.d.ts +1 -0
- package/lib/components/Buttons/variables.dark.js +10 -0
- package/lib/components/Buttons/variables.js +51 -0
- package/lib/components/Catalog/Catalog.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/CatalogFilter/CatalogFilter.d.ts +6 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +35 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.d.ts +6 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +142 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.d.ts +13 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.js +92 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.d.ts +6 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +111 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.d.ts +6 -0
- package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +116 -0
- package/lib/components/Catalog/CatalogSelector.js +0 -1
- package/lib/components/Catalog/variables.js +0 -1
- package/lib/components/Filter/FilterInput.d.ts +1 -0
- package/lib/components/Filter/FilterInput.js +2 -2
- package/lib/components/Filter/FilterOptions.js +2 -0
- package/lib/components/Filter/variables.js +7 -4
- package/lib/components/Search/SearchAiDialog.js +2 -3
- package/lib/components/Search/SearchAiResponse.js +2 -3
- package/lib/components/Search/SearchDialog.d.ts +2 -1
- package/lib/components/Search/SearchDialog.js +2 -2
- 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/hooks/use-tabs.d.ts +11 -6
- package/lib/core/hooks/use-tabs.js +117 -207
- package/lib/core/styles/dark.js +29 -26
- package/lib/core/styles/global.js +64 -59
- package/lib/core/types/l10n.d.ts +1 -1
- 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/RedoclyIcon/RedoclyIcon.d.ts +9 -0
- package/lib/icons/RedoclyIcon/RedoclyIcon.js +27 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/layouts/RootLayout.js +6 -1
- 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 +63 -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 +2 -2
- package/src/components/Accordion/Accordion.tsx +100 -0
- package/src/components/Accordion/AccordionBody.tsx +65 -0
- package/src/components/Accordion/AccordionHeader.tsx +68 -0
- package/src/components/Accordion/AccordionTitle.tsx +26 -0
- package/src/components/Accordion/variables.ts +56 -0
- package/src/components/Buttons/AIAssistantButton.tsx +141 -0
- package/src/components/Buttons/variables.dark.ts +7 -0
- package/src/components/Buttons/variables.ts +48 -0
- package/src/components/Catalog/Catalog.tsx +3 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
- package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +56 -0
- package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +169 -0
- package/src/components/Catalog/CatalogFilter/CatalogFilterContent.tsx +121 -0
- package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +147 -0
- package/src/components/Catalog/CatalogFilter/CatalogFilterSelect.tsx +136 -0
- package/src/components/Catalog/CatalogSelector.tsx +0 -1
- package/src/components/Catalog/variables.ts +0 -1
- package/src/components/Filter/FilterInput.tsx +3 -2
- package/src/components/Filter/FilterOptions.tsx +2 -0
- package/src/components/Filter/variables.ts +7 -4
- package/src/components/Search/SearchAiDialog.tsx +2 -2
- package/src/components/Search/SearchAiResponse.tsx +2 -2
- package/src/components/Search/SearchDialog.tsx +7 -2
- 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/hooks/use-tabs.ts +160 -238
- package/src/core/styles/dark.ts +11 -8
- package/src/core/styles/global.ts +7 -2
- package/src/core/types/l10n.ts +1 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/tabs.ts +4 -0
- package/src/icons/RedoclyIcon/RedoclyIcon.tsx +44 -0
- package/src/index.ts +2 -0
- package/src/layouts/RootLayout.tsx +6 -0
- package/src/markdoc/components/Tabs/Tab.tsx +1 -0
- package/src/markdoc/components/Tabs/TabList.tsx +84 -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
|
+
};
|
package/src/core/styles/dark.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { scorecardDarkMode } from '@redocly/theme/components/Scorecard/variables
|
|
|
4
4
|
import { mermaidDarkMode } from '@redocly/theme/markdoc/components/Mermaid/variables.dark'
|
|
5
5
|
import { menuDarkMode } from '@redocly/theme/components/Menu/variables.dark';
|
|
6
6
|
import { buttonDarkMode } from '@redocly/theme/components/Button/variables.dark';
|
|
7
|
+
import { aiAssistantButtonDarkMode } from '@redocly/theme/components/Buttons/variables.dark';
|
|
7
8
|
import { segmentedButtonsDarkMode } from '@redocly/theme/components/Segmented/variables.dark';
|
|
8
9
|
import { checkboxDarkMode } from '@redocly/theme/icons/CheckboxIcon/variables.dark';
|
|
9
10
|
import { tagDarkMode } from '@redocly/theme/components/Tag/variables.dark';
|
|
@@ -137,14 +138,15 @@ export const darkMode = css`
|
|
|
137
138
|
|
|
138
139
|
--color-green-1: #1a4d40;
|
|
139
140
|
--color-green-2: #195848;
|
|
140
|
-
--color-green-3: #
|
|
141
|
-
--color-green-4: #
|
|
142
|
-
--color-green-5: #
|
|
143
|
-
--color-green-6: #
|
|
144
|
-
--color-green-7: #
|
|
145
|
-
--color-green-8: #
|
|
146
|
-
--color-green-9: #
|
|
147
|
-
--color-green-10: #
|
|
141
|
+
--color-green-3: #136a4d;
|
|
142
|
+
--color-green-4: #0e8450;
|
|
143
|
+
--color-green-5: #149e53;
|
|
144
|
+
--color-green-6: #1cb854;
|
|
145
|
+
--color-green-7: #4dd470;
|
|
146
|
+
--color-green-8: #72e985;
|
|
147
|
+
--color-green-9: #a3f7a9;
|
|
148
|
+
--color-green-10: #d2fbd0;
|
|
149
|
+
--color-green-11: #edfbec;
|
|
148
150
|
|
|
149
151
|
--color-grass-1: #1f4d2d;
|
|
150
152
|
--color-grass-2: #164f29;
|
|
@@ -309,6 +311,7 @@ export const darkMode = css`
|
|
|
309
311
|
|
|
310
312
|
${segmentedButtonsDarkMode}
|
|
311
313
|
${buttonDarkMode}
|
|
314
|
+
${aiAssistantButtonDarkMode}
|
|
312
315
|
${checkboxDarkMode}
|
|
313
316
|
${tagDarkMode}
|
|
314
317
|
${statusCodeDarkMode}
|
|
@@ -11,6 +11,7 @@ import { catalog } from '@redocly/theme/components/Catalog/variables';
|
|
|
11
11
|
import { filter } from '@redocly/theme/components/Filter/variables';
|
|
12
12
|
import { catalogClassic } from '@redocly/theme/components/CatalogClassic/variables';
|
|
13
13
|
import { apiReferencePanels, responsePanelColors } from '@redocly/theme/components/Panel/variables';
|
|
14
|
+
import { accordion } from '@redocly/theme/components/Accordion/variables';
|
|
14
15
|
import { select } from '@redocly/theme/components/Select/variables';
|
|
15
16
|
import { dropdown } from '@redocly/theme/components/Dropdown/variables';
|
|
16
17
|
import { tooltip } from '@redocly/theme/components/Tooltip/variables';
|
|
@@ -18,6 +19,7 @@ import { checkbox } from '@redocly/theme/icons/CheckboxIcon/variables';
|
|
|
18
19
|
import { admonition } from '@redocly/theme/components/Admonition/variables';
|
|
19
20
|
import { footer } from '@redocly/theme/components/Footer/variables';
|
|
20
21
|
import { button } from '@redocly/theme/components/Button/variables';
|
|
22
|
+
import { aiAssistantButton } from '@redocly/theme/components/Buttons/variables';
|
|
21
23
|
import { navbar } from '@redocly/theme/components/Navbar/variables';
|
|
22
24
|
import { search } from '@redocly/theme/components/Search/variables';
|
|
23
25
|
import { menu, mobileMenu } from '@redocly/theme/components/Menu/variables';
|
|
@@ -141,8 +143,9 @@ const themeColors = css`
|
|
|
141
143
|
--color-green-6: #1cb854;
|
|
142
144
|
--color-green-7: #149e53;
|
|
143
145
|
--color-green-8: #0e8450;
|
|
144
|
-
--color-green-9: #
|
|
145
|
-
--color-green-10: #
|
|
146
|
+
--color-green-9: #136a4d;
|
|
147
|
+
--color-green-10: #195848;
|
|
148
|
+
--color-green-11: #1a4d40;
|
|
146
149
|
|
|
147
150
|
--color-grass-1: #f0faeb;
|
|
148
151
|
--color-grass-2: #e3fad6;
|
|
@@ -1227,6 +1230,7 @@ const replay = css`
|
|
|
1227
1230
|
|
|
1228
1231
|
export const styles = css`
|
|
1229
1232
|
:root {
|
|
1233
|
+
${accordion}
|
|
1230
1234
|
${admonition}
|
|
1231
1235
|
${apiReferenceDocs}
|
|
1232
1236
|
${apiReferencePanels}
|
|
@@ -1234,6 +1238,7 @@ export const styles = css`
|
|
|
1234
1238
|
${borders}
|
|
1235
1239
|
${breadcrumbs}
|
|
1236
1240
|
${button}
|
|
1241
|
+
${aiAssistantButton}
|
|
1237
1242
|
${cards}
|
|
1238
1243
|
${catalog}
|
|
1239
1244
|
${catalogClassic}
|
package/src/core/types/l10n.ts
CHANGED
package/src/core/utils/index.ts
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
import { getCssColorVariable } from '@redocly/theme/core/utils';
|
|
7
|
+
|
|
8
|
+
const Icon = (props: IconProps) => (
|
|
9
|
+
<svg
|
|
10
|
+
width="18"
|
|
11
|
+
height="18"
|
|
12
|
+
viewBox="0 0 18 18"
|
|
13
|
+
fill="none"
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<g clipPath="url(#clip0_4053_1165)">
|
|
18
|
+
<path
|
|
19
|
+
d="M14.625 6.19973C14.625 8.69176 12.6173 10.712 10.1406 10.712H2.8125V10.2717C5.04753 10.2717 6.85938 8.44864 6.85938 6.19973C6.85938 3.95082 5.04753 2.12772 2.8125 2.12772V1.6875H10.1406C12.6173 1.6875 14.625 3.70769 14.625 6.19973Z"
|
|
20
|
+
fill="#2467F2"
|
|
21
|
+
/>
|
|
22
|
+
<path
|
|
23
|
+
d="M14.625 16.875C14.625 14.383 12.6173 12.3628 10.1406 12.3628H2.8125V12.803C5.04753 12.803 6.85938 14.6261 6.85938 16.875H14.625Z"
|
|
24
|
+
fill="#2467F2"
|
|
25
|
+
/>
|
|
26
|
+
</g>
|
|
27
|
+
<defs>
|
|
28
|
+
<clipPath id="clip0_4053_1165">
|
|
29
|
+
<rect width="18" height="18" fill="white" />
|
|
30
|
+
</clipPath>
|
|
31
|
+
</defs>
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const RedoclyIcon = styled(Icon).attrs(() => ({
|
|
36
|
+
'data-component-name': 'icons/RedoclyIcon/RedoclyIcon',
|
|
37
|
+
}))<IconProps>`
|
|
38
|
+
path {
|
|
39
|
+
fill: ${({ color }) => getCssColorVariable(color)};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
height: ${({ size }) => size || '16px'};
|
|
43
|
+
width: ${({ size }) => size || '16px'};
|
|
44
|
+
`;
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export * from '@redocly/theme/components/Buttons/CopyButton';
|
|
|
32
32
|
export * from '@redocly/theme/components/Buttons/EditPageButton';
|
|
33
33
|
export * from '@redocly/theme/components/Buttons/EmailButton';
|
|
34
34
|
export * from '@redocly/theme/components/Buttons/NewTabButton';
|
|
35
|
+
export * from '@redocly/theme/components/Buttons/AIAssistantButton';
|
|
35
36
|
/* Markdown */
|
|
36
37
|
export * from '@redocly/theme/components/Markdown/Markdown';
|
|
37
38
|
export * from '@redocly/theme/components/Markdown/styles/baseTable';
|
|
@@ -282,6 +283,7 @@ export * from '@redocly/theme/icons/RabbitMQIcon/RabbitMQIcon';
|
|
|
282
283
|
export * from '@redocly/theme/icons/CurveAutoColonIcon/CurveAutoColonIcon';
|
|
283
284
|
export * from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
284
285
|
export * from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
|
|
286
|
+
export * from '@redocly/theme/icons/RedoclyIcon/RedoclyIcon';
|
|
285
287
|
export * from '@redocly/theme/icons/WorkflowHierarchyIcon/WorkflowHierarchyIcon';
|
|
286
288
|
export * from '@redocly/theme/icons/GenericIcon/GenericIcon';
|
|
287
289
|
export * from '@redocly/theme/icons/ShareIcon/ShareIcon';
|
|
@@ -5,18 +5,24 @@ import type { JSX } from 'react';
|
|
|
5
5
|
import { Navbar } from '@redocly/theme/components/Navbar/Navbar';
|
|
6
6
|
import { Footer } from '@redocly/theme/components/Footer/Footer';
|
|
7
7
|
import { SkipContent } from '@redocly/theme/components/SkipContent/SkipContent';
|
|
8
|
+
import { AIAssistantButton } from '@redocly/theme/components/Buttons/AIAssistantButton';
|
|
9
|
+
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
8
10
|
|
|
9
11
|
export type LayoutConfig = {
|
|
10
12
|
children: React.ReactNode;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export function RootLayout({ children }: LayoutConfig): JSX.Element {
|
|
16
|
+
const { useSearch } = useThemeHooks();
|
|
17
|
+
const { askAi } = useSearch();
|
|
18
|
+
|
|
14
19
|
return (
|
|
15
20
|
<div data-component-name="layouts/RootLayout">
|
|
16
21
|
<SkipContent />
|
|
17
22
|
<Navbar />
|
|
18
23
|
{children}
|
|
19
24
|
<Footer />
|
|
25
|
+
{askAi && <AIAssistantButton />}
|
|
20
26
|
</div>
|
|
21
27
|
);
|
|
22
28
|
}
|