@redocly/theme 0.59.0-next.1 → 0.59.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +7 -1
  2. package/lib/components/Accordion/Accordion.js +17 -7
  3. package/lib/components/Accordion/AccordionBody.js +17 -7
  4. package/lib/components/Admonition/Admonition.js +17 -7
  5. package/lib/components/Badge/Badge.js +17 -7
  6. package/lib/components/Breadcrumbs/Breadcrumb.js +17 -7
  7. package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +17 -7
  8. package/lib/components/Button/Button.js +17 -7
  9. package/lib/components/Buttons/AIAssistantButton.js +23 -9
  10. package/lib/components/Buttons/CopyButton.js +17 -7
  11. package/lib/components/Buttons/variables.js +1 -1
  12. package/lib/components/Catalog/Catalog.d.ts +6 -0
  13. package/lib/components/Catalog/Catalog.js +7 -6
  14. package/lib/components/Catalog/CatalogEntities.js +17 -7
  15. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +17 -7
  16. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +17 -7
  17. package/lib/components/Catalog/CatalogEntity/CatalogEntityInfoBar.js +1 -0
  18. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +17 -7
  19. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
  20. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +17 -7
  21. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
  22. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +17 -7
  23. package/lib/components/Catalog/CatalogEntityIcon.js +2 -1
  24. package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +4 -0
  25. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +17 -7
  26. package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.js +17 -7
  27. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +17 -7
  28. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +17 -7
  29. package/lib/components/Catalog/CatalogSortButton.js +17 -7
  30. package/lib/components/Catalog/CatalogTableView/CatalogTableHeaderCell.js +17 -7
  31. package/lib/components/Catalog/CatalogViewModeToggle.js +17 -7
  32. package/lib/components/Catalog/variables.js +1 -1
  33. package/lib/components/CatalogClassic/CatalogClassicActions.js +17 -7
  34. package/lib/components/CatalogClassic/CatalogClassicCard.js +17 -7
  35. package/lib/components/CatalogClassic/CatalogClassicHighlight.js +17 -7
  36. package/lib/components/CatalogClassic/CatalogClassicVirtualizedGroups.js +17 -7
  37. package/lib/components/CodeBlock/CodeBlock.js +17 -7
  38. package/lib/components/CodeBlock/CodeBlockContainer.js +17 -7
  39. package/lib/components/CodeBlock/CodeBlockTabs.js +17 -7
  40. package/lib/components/Dropdown/Dropdown.d.ts +16 -2
  41. package/lib/components/Dropdown/Dropdown.js +22 -12
  42. package/lib/components/Dropdown/DropdownMenuItem.js +17 -7
  43. package/lib/components/Feedback/Comment.js +17 -7
  44. package/lib/components/Feedback/Feedback.js +17 -7
  45. package/lib/components/Feedback/Mood.js +17 -7
  46. package/lib/components/Feedback/Rating.js +17 -7
  47. package/lib/components/Feedback/Reasons.js +17 -7
  48. package/lib/components/Feedback/Scale.js +17 -7
  49. package/lib/components/Feedback/Sentiment.js +17 -7
  50. package/lib/components/Feedback/Stars.js +17 -7
  51. package/lib/components/Filter/FilterContent.js +17 -7
  52. package/lib/components/Filter/FilterInput.js +17 -7
  53. package/lib/components/Image/Image.js +17 -7
  54. package/lib/components/JsonViewer/JsonViewer.js +17 -7
  55. package/lib/components/JsonViewer/helpers.js +17 -7
  56. package/lib/components/LastUpdated/LastUpdated.js +17 -7
  57. package/lib/components/Link/Link.js +17 -7
  58. package/lib/components/Markdown/Markdown.js +17 -7
  59. package/lib/components/Marker/Marker.js +17 -7
  60. package/lib/components/Menu/MenuContainer.js +17 -7
  61. package/lib/components/Menu/MenuItem.js +18 -8
  62. package/lib/components/Menu/MenuMobile.js +17 -7
  63. package/lib/components/Navbar/NavbarItem.js +3 -3
  64. package/lib/components/PageActions/PageActions.js +17 -7
  65. package/lib/components/PageNavigation/NextButton.js +17 -7
  66. package/lib/components/Panel/Panel.js +17 -7
  67. package/lib/components/Panel/PanelBody.js +17 -7
  68. package/lib/components/Search/FilterFields/SearchFilterFieldSelect.js +17 -7
  69. package/lib/components/Search/FilterFields/SearchFilterFieldTags.js +1 -2
  70. package/lib/components/Search/SearchAiActionButtons.d.ts +10 -0
  71. package/lib/components/Search/SearchAiActionButtons.js +43 -0
  72. package/lib/components/Search/SearchAiConversationInput.d.ts +3 -1
  73. package/lib/components/Search/SearchAiConversationInput.js +56 -14
  74. package/lib/components/Search/SearchAiDialog.d.ts +3 -6
  75. package/lib/components/Search/SearchAiDialog.js +37 -16
  76. package/lib/components/Search/SearchAiMessage.d.ts +9 -5
  77. package/lib/components/Search/SearchAiMessage.js +146 -22
  78. package/lib/components/Search/SearchAiNegativeFeedbackForm.d.ts +8 -0
  79. package/lib/components/Search/SearchAiNegativeFeedbackForm.js +169 -0
  80. package/lib/components/Search/SearchDialog.js +53 -12
  81. package/lib/components/Search/SearchFilter.js +17 -7
  82. package/lib/components/Search/SearchGroups.js +19 -9
  83. package/lib/components/Search/SearchHighlight.js +17 -7
  84. package/lib/components/Search/SearchItem.js +17 -7
  85. package/lib/components/Search/SearchRecent.js +17 -7
  86. package/lib/components/Search/SearchShortcut.js +17 -7
  87. package/lib/components/Search/SearchSuggestedPages.js +17 -7
  88. package/lib/components/Search/SearchTrigger.js +17 -7
  89. package/lib/components/Search/variables.js +36 -64
  90. package/lib/components/Segmented/Segmented.js +17 -7
  91. package/lib/components/Select/Select.js +17 -7
  92. package/lib/components/Select/SelectInput.js +18 -8
  93. package/lib/components/Sidebar/Sidebar.js +17 -7
  94. package/lib/components/SidebarActions/styled.js +17 -7
  95. package/lib/components/SkipContent/SkipContent.js +17 -7
  96. package/lib/components/Switch/Switch.js +17 -7
  97. package/lib/components/TableOfContent/TableOfContent.js +17 -7
  98. package/lib/components/Tag/Tag.d.ts +2 -1
  99. package/lib/components/Tag/Tag.js +67 -18
  100. package/lib/components/Tag/variables.dark.js +135 -36
  101. package/lib/components/Tag/variables.js +78 -61
  102. package/lib/components/Tooltip/Tooltip.js +17 -7
  103. package/lib/components/VersionPicker/VersionPicker.js +17 -7
  104. package/lib/core/constants/search.d.ts +5 -4
  105. package/lib/core/constants/search.js +4 -5
  106. package/lib/core/contexts/CodeSnippetContext.js +17 -7
  107. package/lib/core/hooks/index.d.ts +1 -0
  108. package/lib/core/hooks/index.js +1 -0
  109. package/lib/core/hooks/menu/use-nested-menu.js +1 -1
  110. package/lib/core/hooks/search/use-feedback-tooltip.d.ts +6 -0
  111. package/lib/core/hooks/search/use-feedback-tooltip.js +26 -0
  112. package/lib/core/hooks/use-product-picker.js +2 -1
  113. package/lib/core/hooks/use-tabs.d.ts +3 -2
  114. package/lib/core/hooks/use-tabs.js +115 -57
  115. package/lib/core/hooks/use-telemetry-fallback.d.ts +10 -8
  116. package/lib/core/hooks/use-telemetry-fallback.js +10 -8
  117. package/lib/core/styles/dark.js +4 -0
  118. package/lib/core/styles/global.js +4 -0
  119. package/lib/core/templates/Markdown.js +17 -7
  120. package/lib/core/types/hooks.d.ts +6 -3
  121. package/lib/core/types/l10n.d.ts +1 -1
  122. package/lib/core/types/search.d.ts +11 -4
  123. package/lib/core/types/search.js +6 -0
  124. package/lib/core/utils/download-code-walkthrough.js +17 -7
  125. package/lib/core/utils/frontmatter-translate.d.ts +6 -0
  126. package/lib/core/utils/frontmatter-translate.js +14 -0
  127. package/lib/core/utils/get-file-icon.js +17 -7
  128. package/lib/core/utils/index.d.ts +1 -0
  129. package/lib/core/utils/index.js +1 -0
  130. package/lib/icons/AiStarsGradientIcon/AiStarsGradientIcon.js +44 -4
  131. package/lib/icons/AiStarsIcon/AiStarsIcon.js +11 -2
  132. package/lib/icons/CubeIcon/CubeIcon.d.ts +9 -0
  133. package/lib/icons/CubeIcon/CubeIcon.js +17 -0
  134. package/lib/icons/GenericIcon/GenericIcon.js +17 -7
  135. package/lib/icons/HashtagIcon/HashtagIcon.d.ts +9 -0
  136. package/lib/icons/HashtagIcon/HashtagIcon.js +22 -0
  137. package/lib/icons/RedoclyIcon/RedoclyIcon.js +4 -7
  138. package/lib/icons/Spinner/Spinner.js +17 -7
  139. package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.d.ts +9 -0
  140. package/lib/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.js +34 -0
  141. package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.d.ts +9 -0
  142. package/lib/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.js +34 -0
  143. package/lib/index.d.ts +1 -0
  144. package/lib/index.js +18 -7
  145. package/lib/layouts/OIDCForbidden.js +17 -7
  146. package/lib/layouts/ThreePanelLayout.js +17 -7
  147. package/lib/markdoc/components/Cards/Card.js +1 -28
  148. package/lib/markdoc/components/Cards/Cards.js +17 -7
  149. package/lib/markdoc/components/CodeGroup/CodeGroup.js +17 -7
  150. package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +17 -7
  151. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +17 -7
  152. package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.js +17 -7
  153. package/lib/markdoc/components/CodeWalkthrough/CodePanelPreview.js +17 -7
  154. package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.js +17 -7
  155. package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +17 -7
  156. package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +17 -7
  157. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +17 -7
  158. package/lib/markdoc/components/CodeWalkthrough/Input.js +17 -7
  159. package/lib/markdoc/components/Heading/Heading.js +17 -7
  160. package/lib/markdoc/components/HtmlBlock/HtmlBlock.js +17 -7
  161. package/lib/markdoc/components/InlineSvg/InlineSvg.js +17 -7
  162. package/lib/markdoc/components/MarkdocExample/MarkdocExample.js +17 -7
  163. package/lib/markdoc/components/Tabs/TabList.d.ts +3 -1
  164. package/lib/markdoc/components/Tabs/TabList.js +214 -54
  165. package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -1
  166. package/lib/markdoc/components/Tabs/Tabs.js +74 -19
  167. package/lib/markdoc/default.d.ts +104 -1
  168. package/lib/markdoc/default.js +17 -7
  169. package/lib/markdoc/tags/card.js +0 -1
  170. package/package.json +8 -8
  171. package/src/components/Buttons/AIAssistantButton.tsx +6 -2
  172. package/src/components/Buttons/variables.ts +1 -1
  173. package/src/components/Catalog/Catalog.tsx +15 -4
  174. package/src/components/Catalog/CatalogEntity/CatalogEntityInfoBar.tsx +1 -0
  175. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
  176. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
  177. package/src/components/Catalog/CatalogEntityIcon.tsx +2 -1
  178. package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +5 -0
  179. package/src/components/Catalog/variables.ts +1 -1
  180. package/src/components/Dropdown/Dropdown.tsx +84 -79
  181. package/src/components/Menu/MenuItem.tsx +1 -0
  182. package/src/components/Navbar/NavbarItem.tsx +6 -5
  183. package/src/components/Search/FilterFields/SearchFilterFieldTags.tsx +3 -3
  184. package/src/components/Search/SearchAiActionButtons.tsx +76 -0
  185. package/src/components/Search/SearchAiConversationInput.tsx +61 -18
  186. package/src/components/Search/SearchAiDialog.tsx +52 -23
  187. package/src/components/Search/SearchAiMessage.tsx +172 -43
  188. package/src/components/Search/SearchAiNegativeFeedbackForm.tsx +210 -0
  189. package/src/components/Search/SearchDialog.tsx +49 -13
  190. package/src/components/Search/SearchGroups.tsx +2 -0
  191. package/src/components/Search/variables.ts +36 -64
  192. package/src/components/Select/SelectInput.tsx +1 -0
  193. package/src/components/Tag/Tag.tsx +36 -20
  194. package/src/components/Tag/variables.dark.ts +135 -36
  195. package/src/components/Tag/variables.ts +78 -61
  196. package/src/core/constants/search.ts +8 -4
  197. package/src/core/hooks/index.ts +1 -0
  198. package/src/core/hooks/menu/use-nested-menu.ts +2 -2
  199. package/src/core/hooks/search/use-feedback-tooltip.ts +32 -0
  200. package/src/core/hooks/use-product-picker.ts +2 -1
  201. package/src/core/hooks/use-tabs.ts +168 -86
  202. package/src/core/hooks/use-telemetry-fallback.ts +10 -8
  203. package/src/core/styles/dark.ts +4 -0
  204. package/src/core/styles/global.ts +4 -0
  205. package/src/core/types/hooks.ts +6 -1
  206. package/src/core/types/l10n.ts +5 -0
  207. package/src/core/types/search.ts +13 -4
  208. package/src/core/utils/frontmatter-translate.ts +9 -0
  209. package/src/core/utils/index.ts +1 -0
  210. package/src/icons/AiStarsGradientIcon/AiStarsGradientIcon.tsx +13 -4
  211. package/src/icons/AiStarsIcon/AiStarsIcon.tsx +11 -2
  212. package/src/icons/CubeIcon/CubeIcon.tsx +27 -0
  213. package/src/icons/HashtagIcon/HashtagIcon.tsx +23 -0
  214. package/src/icons/RedoclyIcon/RedoclyIcon.tsx +4 -22
  215. package/src/icons/ThumbDownFilledIcon/ThumbDownFilledIcon.tsx +38 -0
  216. package/src/icons/ThumbUpFilledIcon/ThumbUpFilledIcon.tsx +35 -0
  217. package/src/index.ts +1 -0
  218. package/src/markdoc/components/Cards/Card.tsx +1 -28
  219. package/src/markdoc/components/Tabs/TabList.tsx +312 -105
  220. package/src/markdoc/components/Tabs/Tabs.tsx +136 -11
  221. package/src/markdoc/tags/card.ts +0 -1
@@ -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({ activeTab, onTabChange, totalTabs, containerRef }: UseTabsProps) {
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
- const allTabsHidden = useMemo(() => tabs.visible.length === 0, [tabs.visible]);
51
+ // Synchronously update ref before any callbacks or effects run
52
+ activeTabRef.current = activeTab;
30
53
 
31
- const setTabRef = useCallback((element: HTMLButtonElement | null, index: number) => {
32
- tabRefs.current[index] = element;
54
+ const setTabRef = useCallback(
55
+ (element: HTMLButtonElement | null, index: number) => {
56
+ tabRefs.current[index] = element;
33
57
 
34
- const width = element?.offsetWidth;
35
- if (width) {
36
- tabWidthsRef.current[index] = width;
37
- }
58
+ const width = element?.offsetWidth;
59
+ if (width) {
60
+ tabWidthsRef.current[index] = width;
61
+ }
38
62
 
39
- const label = element?.getAttribute('data-label');
40
- if (label) {
41
- tabLabelsRef.current[index] = label;
42
- }
43
- }, []);
63
+ const label = element?.getAttribute('data-label');
64
+ if (label) {
65
+ tabLabelsRef.current[index] = label;
66
+ }
44
67
 
45
- const focusTab = (index: number) => {
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
- (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
- }
116
+ const replaceLastVisibleTabWithClickedOverflowTab = useCallback((clickedIndex: number) => {
117
+ setTabs((prevTabs) => {
118
+ const { visible: visibleTabs, overflow: overflowTabs } = prevTabs;
96
119
 
97
- setTabs({
98
- visible: newVisibleTabs,
99
- overflow: newOverflowTabs,
100
- });
101
- },
102
- [tabs],
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 hasOverflowTabs = tabs.overflow.length > 0;
115
- if (hasOverflowTabs && !allTabsHidden && tabs.overflow.includes(clickedIndex)) {
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
- const label = tabLabelsRef.current[clickedIndex];
120
- if (label) {
121
- onTabChange(label);
122
- focusTab(clickedIndex);
123
- }
147
+ onTabChange(label);
148
+ focusTab(clickedIndex);
124
149
  },
125
- [allTabsHidden, tabs.overflow, onTabChange, replaceLastVisibleTabWithClickedOverflowTab],
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 activeTabIndex = tabRefs.current.findIndex(
135
- (ref) => ref?.getAttribute('data-label') === activeTab,
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 visible = activeTabIndex !== -1 ? [activeTabIndex] : [];
141
- const overflow = [];
176
+ const visibleTabs = activeTabIndex !== -1 ? [activeTabIndex] : [];
177
+ const overflowTabs = [];
142
178
 
143
179
  for (let i = 0; i < tabWidths.length; i++) {
144
- // Skip active tab, it was added initially
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
- visible.push(i);
186
+ visibleTabs.push(i);
154
187
  tabsWidth += tabWidthWithGap;
155
188
  } else {
156
- overflow.push(i);
189
+ overflowTabs.push(i);
157
190
  }
158
191
  }
159
192
 
160
- if (overflow.length > 0) {
193
+ if (overflowTabs.length > 0) {
161
194
  tabsWidth += MORE_BUTTON_WIDTH;
162
195
 
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) {
165
- const removed = visible.pop();
166
- if (removed !== undefined) {
167
- overflow.unshift(removed);
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
- if (!containerRef?.current) return;
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
- allTabsHidden,
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 [activeTab, setActiveTab] = useState(getInitialTab({ initialTab, searchParams, tabsId }));
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
- activeTab,
233
- setActiveTab,
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
- sendOpenapiDocsViewedMessage: () => {},
47
- sendOpenapiDocsPerformanceMetricsMessage: () => {},
48
- sendOpenapiDocsDownloadDefinitionClickedMessage: () => {},
49
- sendOpenapiDocsSelectLanguageClickedMessage: () => {},
50
- sendOpenapiDocsExpandCollapseAllClickedMessage: () => {},
51
- sendOpenapiDocsSwitchServersClickedMessage: () => {},
52
- sendOpenapiDocsExamplesSwitcherClickedMessage: () => {},
53
- sendOpenapiDocsTryItOpenedMessage: () => {},
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: () => {},
@@ -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
 
@@ -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
 
@@ -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: (options?: { filter?: SearchFilterItem[] }) => {
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;
@@ -142,6 +146,7 @@ export type ThemeHooks = {
142
146
  | undefined;
143
147
  useCatalog: (
144
148
  config?: CatalogEntityConfig,
149
+ serverFilters?: Record<string, { value: string; count: number }[]>,
145
150
  entitiesCounterInitial?: number,
146
151
  initialViewMode?: CatalogViewMode,
147
152
  ) => UseCatalogResponse;
@@ -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'
@@ -146,6 +150,7 @@ export type TranslationKey =
146
150
  | 'catalog.entity.metadata.title'
147
151
  | 'catalog.entity.schema.title'
148
152
  | 'catalog.entity.properties.apiDescription.title'
153
+ | 'catalog.backToAllLabel'
149
154
  | 'sidebar.menu.backLabel'
150
155
  | 'sidebar.menu.backToLabel'
151
156
  | 'sidebar.actions.show'
@@ -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
- url: string;
101
- title: string;
102
- }[];
109
+ resources?: SearchAiMessageResource[];
110
+ messageId?: string;
111
+ feedback?: FeedbackType;
103
112
  };
@@ -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
+ };
@@ -38,3 +38,4 @@ export * from './enhanced-smoothstep';
38
38
  export * from './icon-resolver';
39
39
  export * from './dynamic';
40
40
  export * from './tabs';
41
+ export * from './frontmatter-translate';
@@ -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.startsWith('var(')
19
- ? getComputedStyle(document.documentElement).getPropertyValue(color.slice(4, -1)).trim()
20
- : color;
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)';
@@ -35,19 +35,28 @@ export const AiStarsIcon = styled(Icon).attrs(() => ({
35
35
  height: ${({ size }) => size || '16px'};
36
36
  width: ${({ size }) => size || '16px'};
37
37
 
38
- ${({ background, borderRadius, margin }) =>
38
+ ${({ background, borderRadius, margin, padding, size }) =>
39
39
  background &&
40
40
  `
41
41
  display: flex;
42
42
  align-items: center;
43
43
  justify-content: center;
44
+ flex-shrink: 0;
44
45
 
45
46
  background: ${getCssColorVariable(background)};
47
+ width: ${size || '16px'};
48
+ height: ${size || '16px'};
46
49
 
47
- padding: var(--spacing-xs);
50
+ padding: ${padding || 'var(--spacing-xs)'};
48
51
  margin: ${margin || '0'};
49
52
 
50
53
  border-radius: ${background && borderRadius ? borderRadius : 'none'};
54
+
55
+ svg {
56
+ width: calc(${size || '16px'} - 2 * (${padding || 'var(--spacing-xs)'}));
57
+ height: calc(${size || '16px'} - 2 * (${padding || 'var(--spacing-xs)'}));
58
+ flex-shrink: 0;
59
+ }
51
60
  `}
52
61
 
53
62
  color: ${({ color }) => color && getCssColorVariable(color)};
@@ -0,0 +1,27 @@
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
+ xmlns="http://www.w3.org/2000/svg"
9
+ width="16"
10
+ height="16"
11
+ viewBox="0 0 16 16"
12
+ fill="currentColor"
13
+ {...props}
14
+ >
15
+ <path
16
+ d="M14.252 4.06812L8.25195 0.568119C8.17548 0.523507 8.08853 0.5 8 0.5C7.91147 0.5 7.82452 0.523507 7.74805 0.568119L1.74805 4.06812C1.67257 4.11215 1.60994 4.1752 1.56642 4.25099C1.5229 4.32677 1.5 4.41263 1.5 4.50002V11.5C1.5 11.5874 1.5229 11.6733 1.56642 11.7491C1.60994 11.8248 1.67257 11.8879 1.74805 11.9319L7.74805 15.4319C7.82452 15.4765 7.91147 15.5 8 15.5C8.08853 15.5 8.17548 15.4765 8.25195 15.4319L14.252 11.9319C14.3274 11.8879 14.3901 11.8248 14.4336 11.7491C14.4771 11.6733 14.5 11.5874 14.5 11.5V4.50002C14.5 4.41263 14.4771 4.32677 14.4336 4.25099C14.3901 4.1752 14.3274 4.11215 14.252 4.06812ZM8 1.57887L13.0078 4.50002L8 7.42117L2.9922 4.50002L8 1.57887ZM2.5 5.37062L7.5 8.28712V14.1294L2.5 11.2129V5.37062ZM8.5 14.1294V8.28712L13.5 5.37062V11.2129L8.5 14.1294Z"
17
+ fill="currentColor"
18
+ />
19
+ </svg>
20
+ );
21
+
22
+ export const CubeIcon = styled(Icon).attrs(() => ({
23
+ 'data-component-name': 'icons/CubeIcon/CubeIcon',
24
+ }))<IconProps>`
25
+ height: ${({ size }) => size || '16px'};
26
+ width: ${({ size }) => size || '16px'};
27
+ `;
@@ -0,0 +1,23 @@
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 viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path d="M10.5 4.5V3.75H8.25V1.5H7.5V3.75H4.5V1.5H3.75V3.75H1.5V4.5H3.75V7.5H1.5V8.25H3.75V10.5H4.5V8.25H7.5V10.5H8.25V8.25H10.5V7.5H8.25V4.5H10.5ZM7.5 7.5H4.5V4.5H7.5V7.5Z" />
11
+ </svg>
12
+ );
13
+
14
+ export const HashtagIcon = styled(Icon).attrs(() => ({
15
+ 'data-component-name': 'icons/HashtagIcon/HashtagIcon',
16
+ }))<IconProps>`
17
+ path {
18
+ fill: ${({ color }) => getCssColorVariable(color)};
19
+ }
20
+
21
+ height: ${({ size }) => size || '16px'};
22
+ width: ${({ size }) => size || '16px'};
23
+ `;
@@ -6,29 +6,11 @@ import type { IconProps } from '@redocly/theme/icons/types';
6
6
  import { getCssColorVariable } from '@redocly/theme/core/utils';
7
7
 
8
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
- />
9
+ <svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <g>
11
+ <path 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" />
12
+ <path 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" />
26
13
  </g>
27
- <defs>
28
- <clipPath id="clip0_4053_1165">
29
- <rect width="18" height="18" fill="white" />
30
- </clipPath>
31
- </defs>
32
14
  </svg>
33
15
  );
34
16