@redocly/theme 0.70.0-next.1 → 0.70.0-next.11

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 (378) hide show
  1. package/lib/components/Buttons/ConnectMCPButton.js +19 -14
  2. package/lib/components/Buttons/variables.d.ts +1 -0
  3. package/lib/components/Buttons/variables.js +4 -1
  4. package/lib/components/Catalog/Catalog.js +3 -3
  5. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.d.ts +1 -1
  6. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  8. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
  9. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +30 -11
  10. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
  11. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
  12. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
  13. package/lib/components/CodeBlock/CodeBlockControls.js +3 -5
  14. package/lib/components/Consent/Consent.d.ts +2 -0
  15. package/lib/components/Consent/Consent.js +17 -0
  16. package/lib/components/Consent/ConsentBanner.d.ts +2 -0
  17. package/lib/components/Consent/ConsentBanner.js +120 -0
  18. package/lib/components/Consent/ConsentDialog.d.ts +2 -0
  19. package/lib/components/Consent/ConsentDialog.js +128 -0
  20. package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
  21. package/lib/components/Consent/ConsentSettingsLink.js +35 -0
  22. package/lib/components/Consent/variables.d.ts +1 -0
  23. package/lib/components/Consent/variables.js +31 -0
  24. package/lib/components/Dropdown/Dropdown.d.ts +4 -2
  25. package/lib/components/Dropdown/Dropdown.js +31 -9
  26. package/lib/components/Dropdown/DropdownMenu.d.ts +1 -0
  27. package/lib/components/Dropdown/DropdownMenu.js +1 -1
  28. package/lib/components/Feedback/Reasons.js +3 -2
  29. package/lib/components/Filter/FilterCheckboxes.js +30 -11
  30. package/lib/components/Filter/FilterDateRange.js +36 -2
  31. package/lib/components/Footer/Footer.js +4 -1
  32. package/lib/components/Image/Image.js +24 -4
  33. package/lib/components/JsonViewer/Helpers.d.ts +10 -2
  34. package/lib/components/JsonViewer/Helpers.js +32 -36
  35. package/lib/components/JsonViewer/JsonViewer.js +15 -18
  36. package/lib/components/LoadProgressBar/LoadProgressBar.d.ts +8 -0
  37. package/lib/components/LoadProgressBar/LoadProgressBar.js +122 -0
  38. package/lib/components/MCP/McpConnectionTabs.js +9 -2
  39. package/lib/components/Markdown/Markdown.js +3 -2
  40. package/lib/components/Markdown/styles/base-table.js +14 -1
  41. package/lib/components/Markdown/variables.js +1 -0
  42. package/lib/components/Menu/Menu.js +4 -0
  43. package/lib/components/Menu/MenuSelect.d.ts +7 -0
  44. package/lib/components/Menu/MenuSelect.js +129 -0
  45. package/lib/components/Menu/variables.js +43 -4
  46. package/lib/components/Navbar/Navbar.js +44 -5
  47. package/lib/components/Navbar/variables.js +3 -0
  48. package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
  49. package/lib/components/NavigationTabs/NavigationTab.js +93 -0
  50. package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
  51. package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
  52. package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
  53. package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
  54. package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
  55. package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
  56. package/lib/components/NavigationTabs/variables.d.ts +1 -0
  57. package/lib/components/NavigationTabs/variables.js +37 -0
  58. package/lib/components/Portal/Portal.js +37 -5
  59. package/lib/components/Product/utils.js +1 -1
  60. package/lib/components/Search/SearchAiActionButtons.js +8 -3
  61. package/lib/components/Search/SearchAiActivity.d.ts +10 -0
  62. package/lib/components/Search/SearchAiActivity.js +278 -0
  63. package/lib/components/Search/SearchAiConversationInput.js +10 -6
  64. package/lib/components/Search/SearchAiDialog.js +13 -2
  65. package/lib/components/Search/SearchAiMessage.d.ts +2 -1
  66. package/lib/components/Search/SearchAiMessage.js +21 -109
  67. package/lib/components/Search/SearchAiResponse.js +1 -0
  68. package/lib/components/Search/SearchDialog.js +10 -0
  69. package/lib/components/Search/SearchItem.js +11 -10
  70. package/lib/components/Search/SearchTrigger.js +37 -6
  71. package/lib/components/Search/variables.js +17 -2
  72. package/lib/components/Segmented/Segmented.js +23 -3
  73. package/lib/components/Select/Select.js +10 -3
  74. package/lib/components/Select/SelectInput.d.ts +1 -0
  75. package/lib/components/Select/SelectInput.js +5 -11
  76. package/lib/components/Sidebar/Sidebar.js +2 -2
  77. package/lib/components/SplitButton/SplitButton.js +37 -3
  78. package/lib/components/TableOfContent/TableOfContent.js +2 -2
  79. package/lib/core/constants/common.d.ts +1 -0
  80. package/lib/core/constants/common.js +1 -0
  81. package/lib/core/constants/consent.d.ts +2 -0
  82. package/lib/core/constants/consent.js +30 -0
  83. package/lib/core/constants/index.d.ts +2 -0
  84. package/lib/core/constants/index.js +2 -0
  85. package/lib/core/constants/mcp.d.ts +2 -2
  86. package/lib/core/constants/mcp.js +3 -8
  87. package/lib/core/constants/prompt.d.ts +9 -0
  88. package/lib/core/constants/prompt.js +33 -0
  89. package/lib/core/constants/search.d.ts +1 -1
  90. package/lib/core/constants/search.js +6 -2
  91. package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
  92. package/lib/core/contexts/PortalContainerContext.js +7 -0
  93. package/lib/core/hooks/code-walkthrough/use-code-panel.d.ts +1 -1
  94. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +1 -1
  95. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +1 -1
  96. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.d.ts +1 -1
  97. package/lib/core/hooks/code-walkthrough/use-code-walkthrough.d.ts +1 -2
  98. package/lib/core/hooks/code-walkthrough/use-renderable-files.d.ts +1 -1
  99. package/lib/core/hooks/index.d.ts +9 -0
  100. package/lib/core/hooks/index.js +9 -0
  101. package/lib/core/hooks/menu/use-collapse.js +1 -1
  102. package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
  103. package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
  104. package/lib/core/hooks/prompt/use-prompt-actions.d.ts +7 -0
  105. package/lib/core/hooks/prompt/use-prompt-actions.js +19 -0
  106. package/lib/core/hooks/prompt/use-prompt-text-limit.d.ts +19 -0
  107. package/lib/core/hooks/prompt/use-prompt-text-limit.js +35 -0
  108. package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
  109. package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
  110. package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
  111. package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
  112. package/lib/core/hooks/use-connect-mcp-button.js +3 -1
  113. package/lib/core/hooks/use-consent-dialog.d.ts +12 -0
  114. package/lib/core/hooks/use-consent-dialog.js +36 -0
  115. package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
  116. package/lib/core/hooks/use-consent-telemetry.js +42 -0
  117. package/lib/core/hooks/use-dialog-hotkeys.js +4 -2
  118. package/lib/core/hooks/use-full-height.d.ts +1 -1
  119. package/lib/core/hooks/use-full-height.js +8 -8
  120. package/lib/core/hooks/use-page-actions.d.ts +1 -1
  121. package/lib/core/hooks/use-page-actions.js +10 -8
  122. package/lib/core/hooks/use-pointer-border-highlight.d.ts +7 -0
  123. package/lib/core/hooks/use-pointer-border-highlight.js +29 -0
  124. package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
  125. package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
  126. package/lib/core/hooks/use-tab-list.d.ts +31 -0
  127. package/lib/core/hooks/use-tab-list.js +49 -0
  128. package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
  129. package/lib/core/hooks/use-telemetry-fallback.js +2 -0
  130. package/lib/core/hooks/use-theme-hooks.js +16 -0
  131. package/lib/core/styles/dark.js +14 -12
  132. package/lib/core/styles/global.d.ts +2 -0
  133. package/lib/core/styles/global.js +115 -136
  134. package/lib/core/styles/index.d.ts +1 -0
  135. package/lib/core/styles/index.js +3 -1
  136. package/lib/core/styles/search-ai-appear.d.ts +1 -0
  137. package/lib/core/styles/search-ai-appear.js +18 -0
  138. package/lib/core/types/code-walkthrough.d.ts +84 -0
  139. package/lib/core/types/consent.d.ts +54 -0
  140. package/lib/core/types/consent.js +3 -0
  141. package/lib/core/types/dropdown.d.ts +1 -0
  142. package/lib/core/types/dropdown.js +3 -0
  143. package/lib/core/types/hooks.d.ts +8 -1
  144. package/lib/core/types/index.d.ts +4 -0
  145. package/lib/core/types/l10n.d.ts +1 -1
  146. package/lib/core/types/mcp.d.ts +3 -2
  147. package/lib/core/types/navigation-tabs.d.ts +49 -0
  148. package/lib/core/types/navigation-tabs.js +3 -0
  149. package/lib/core/types/prompt.d.ts +9 -0
  150. package/lib/core/types/prompt.js +3 -0
  151. package/lib/core/types/search.d.ts +25 -0
  152. package/lib/core/utils/Dynamic.d.ts +8 -1
  153. package/lib/core/utils/Dynamic.js +34 -3
  154. package/lib/core/utils/assign-ref.d.ts +2 -0
  155. package/lib/core/utils/assign-ref.js +12 -0
  156. package/lib/core/utils/consent-settings-link-placement.d.ts +3 -0
  157. package/lib/core/utils/consent-settings-link-placement.js +15 -0
  158. package/lib/core/utils/content-segments.d.ts +12 -1
  159. package/lib/core/utils/content-segments.js +90 -5
  160. package/lib/core/utils/download-code-walkthrough.d.ts +1 -1
  161. package/lib/core/utils/dropdown.d.ts +2 -0
  162. package/lib/core/utils/dropdown.js +12 -0
  163. package/lib/core/utils/get-ai-prompt-link.d.ts +15 -0
  164. package/lib/core/utils/get-ai-prompt-link.js +29 -0
  165. package/lib/core/utils/get-code-walkthrough-file-text.d.ts +1 -1
  166. package/lib/core/utils/index.d.ts +5 -0
  167. package/lib/core/utils/index.js +5 -0
  168. package/lib/core/utils/match-code-walkthrough-conditions.d.ts +1 -1
  169. package/lib/core/utils/mcp.d.ts +2 -1
  170. package/lib/core/utils/mcp.js +13 -3
  171. package/lib/core/utils/menu.d.ts +4 -0
  172. package/lib/core/utils/menu.js +48 -8
  173. package/lib/core/utils/navigation-tabs.d.ts +8 -0
  174. package/lib/core/utils/navigation-tabs.js +71 -0
  175. package/lib/core/utils/search-ai-activity.d.ts +4 -0
  176. package/lib/core/utils/search-ai-activity.js +83 -0
  177. package/lib/core/utils/urls.js +3 -1
  178. package/lib/core/utils/with-load-progress.d.ts +3 -0
  179. package/lib/core/utils/with-load-progress.js +24 -19
  180. package/lib/ext/configure.d.ts +1 -1
  181. package/lib/ext/configure.js +0 -1
  182. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
  183. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
  184. package/lib/icons/LinkIcon/LinkIconSymbol.d.ts +12 -0
  185. package/lib/icons/LinkIcon/LinkIconSymbol.js +41 -0
  186. package/lib/index.d.ts +7 -0
  187. package/lib/index.js +9 -1
  188. package/lib/layouts/PageLayout.js +1 -1
  189. package/lib/layouts/RootLayout.js +2 -2
  190. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.d.ts +1 -1
  191. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
  192. package/lib/markdoc/components/CodeWalkthrough/CodePanel.d.ts +1 -1
  193. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
  194. package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.d.ts +1 -1
  195. package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.d.ts +1 -1
  196. package/lib/markdoc/components/CodeWalkthrough/CodeStep.d.ts +1 -1
  197. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.d.ts +1 -1
  198. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
  199. package/lib/markdoc/components/Prompt/Prompt.d.ts +15 -0
  200. package/lib/markdoc/components/Prompt/Prompt.js +163 -0
  201. package/lib/markdoc/components/Prompt/PromptActions.d.ts +13 -0
  202. package/lib/markdoc/components/Prompt/PromptActions.js +43 -0
  203. package/lib/markdoc/components/Prompt/PromptBody.d.ts +14 -0
  204. package/lib/markdoc/components/Prompt/PromptBody.js +99 -0
  205. package/lib/markdoc/components/Prompt/PromptCopyButton.d.ts +10 -0
  206. package/lib/markdoc/components/Prompt/PromptCopyButton.js +28 -0
  207. package/lib/markdoc/components/Prompt/PromptHeader.d.ts +9 -0
  208. package/lib/markdoc/components/Prompt/PromptHeader.js +57 -0
  209. package/lib/markdoc/components/Prompt/PromptShowHideButton.d.ts +7 -0
  210. package/lib/markdoc/components/Prompt/PromptShowHideButton.js +21 -0
  211. package/lib/markdoc/components/Prompt/PromptToggleLabel.d.ts +7 -0
  212. package/lib/markdoc/components/Prompt/PromptToggleLabel.js +28 -0
  213. package/lib/markdoc/components/Prompt/variables.d.ts +1 -0
  214. package/lib/markdoc/components/Prompt/variables.dark.d.ts +1 -0
  215. package/lib/markdoc/components/Prompt/variables.dark.js +8 -0
  216. package/lib/markdoc/components/Prompt/variables.js +44 -0
  217. package/lib/markdoc/components/Tabs/TabList.js +8 -6
  218. package/lib/markdoc/components/default.d.ts +1 -0
  219. package/lib/markdoc/components/default.js +1 -0
  220. package/lib/markdoc/default.d.ts +2 -0
  221. package/lib/markdoc/default.js +2 -0
  222. package/lib/markdoc/tags/code-walkthrough.d.ts +1 -1
  223. package/lib/markdoc/tags/connect-mcp.js +15 -0
  224. package/lib/markdoc/tags/prompt.d.ts +2 -0
  225. package/lib/markdoc/tags/prompt.js +56 -0
  226. package/package.json +4 -8
  227. package/src/components/Buttons/ConnectMCPButton.tsx +21 -16
  228. package/src/components/Buttons/variables.ts +4 -0
  229. package/src/components/Catalog/Catalog.tsx +6 -4
  230. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
  231. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  232. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
  233. package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +37 -22
  234. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
  235. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
  236. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
  237. package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
  238. package/src/components/Consent/Consent.tsx +18 -0
  239. package/src/components/Consent/ConsentBanner.tsx +110 -0
  240. package/src/components/Consent/ConsentDialog.tsx +210 -0
  241. package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
  242. package/src/components/Consent/variables.ts +28 -0
  243. package/src/components/Dropdown/Dropdown.tsx +32 -7
  244. package/src/components/Dropdown/DropdownMenu.tsx +7 -1
  245. package/src/components/Feedback/Reasons.tsx +3 -2
  246. package/src/components/Filter/FilterCheckboxes.tsx +33 -18
  247. package/src/components/Filter/FilterDateRange.tsx +3 -2
  248. package/src/components/Footer/Footer.tsx +2 -0
  249. package/src/components/Image/Image.tsx +26 -5
  250. package/src/components/JsonViewer/Helpers.tsx +51 -54
  251. package/src/components/JsonViewer/JsonViewer.tsx +23 -25
  252. package/src/components/LoadProgressBar/LoadProgressBar.tsx +113 -0
  253. package/src/components/MCP/McpConnectionTabs.tsx +22 -5
  254. package/src/components/Markdown/Markdown.tsx +3 -2
  255. package/src/components/Markdown/styles/base-table.ts +14 -1
  256. package/src/components/Markdown/variables.ts +1 -0
  257. package/src/components/Menu/Menu.tsx +5 -0
  258. package/src/components/Menu/MenuSelect.tsx +198 -0
  259. package/src/components/Menu/variables.ts +43 -4
  260. package/src/components/Navbar/Navbar.tsx +22 -5
  261. package/src/components/Navbar/variables.ts +3 -0
  262. package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
  263. package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
  264. package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
  265. package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
  266. package/src/components/NavigationTabs/variables.ts +34 -0
  267. package/src/components/Portal/Portal.tsx +4 -2
  268. package/src/components/Product/utils.ts +1 -1
  269. package/src/components/Search/SearchAiActionButtons.tsx +31 -19
  270. package/src/components/Search/SearchAiActivity.tsx +318 -0
  271. package/src/components/Search/SearchAiConversationInput.tsx +13 -7
  272. package/src/components/Search/SearchAiDialog.tsx +20 -4
  273. package/src/components/Search/SearchAiMessage.tsx +28 -136
  274. package/src/components/Search/SearchAiResponse.tsx +1 -0
  275. package/src/components/Search/SearchDialog.tsx +11 -0
  276. package/src/components/Search/SearchItem.tsx +19 -10
  277. package/src/components/Search/SearchTrigger.tsx +47 -3
  278. package/src/components/Search/variables.ts +17 -2
  279. package/src/components/Segmented/Segmented.tsx +25 -16
  280. package/src/components/Select/Select.tsx +12 -6
  281. package/src/components/Select/SelectInput.tsx +10 -18
  282. package/src/components/Sidebar/Sidebar.tsx +2 -2
  283. package/src/components/SplitButton/SplitButton.tsx +8 -2
  284. package/src/components/TableOfContent/TableOfContent.tsx +2 -2
  285. package/src/core/constants/common.ts +1 -0
  286. package/src/core/constants/consent.ts +29 -0
  287. package/src/core/constants/index.ts +2 -0
  288. package/src/core/constants/mcp.ts +3 -9
  289. package/src/core/constants/prompt.ts +34 -0
  290. package/src/core/constants/search.ts +6 -2
  291. package/src/core/contexts/PortalContainerContext.tsx +4 -0
  292. package/src/core/hooks/__mocks__/index.ts +5 -0
  293. package/src/core/hooks/__mocks__/use-theme-hooks.ts +20 -0
  294. package/src/core/hooks/code-walkthrough/__mocks__/MockIntersectionObserver.ts +1 -2
  295. package/src/core/hooks/code-walkthrough/__mocks__/mock-intersection-observer.ts +1 -2
  296. package/src/core/hooks/code-walkthrough/use-code-panel.ts +1 -1
  297. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +2 -2
  298. package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +1 -1
  299. package/src/core/hooks/code-walkthrough/use-code-walkthrough.ts +3 -3
  300. package/src/core/hooks/code-walkthrough/use-renderable-files.ts +1 -1
  301. package/src/core/hooks/index.ts +9 -0
  302. package/src/core/hooks/menu/use-collapse.ts +1 -1
  303. package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
  304. package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
  305. package/src/core/hooks/prompt/use-prompt-actions.ts +28 -0
  306. package/src/core/hooks/prompt/use-prompt-text-limit.ts +59 -0
  307. package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
  308. package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
  309. package/src/core/hooks/use-connect-mcp-button.ts +13 -2
  310. package/src/core/hooks/use-consent-dialog.ts +51 -0
  311. package/src/core/hooks/use-consent-telemetry.ts +56 -0
  312. package/src/core/hooks/use-dialog-hotkeys.ts +11 -5
  313. package/src/core/hooks/use-full-height.ts +8 -8
  314. package/src/core/hooks/use-page-actions.ts +21 -9
  315. package/src/core/hooks/use-pointer-border-highlight.ts +46 -0
  316. package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
  317. package/src/core/hooks/use-tab-list.ts +63 -0
  318. package/src/core/hooks/use-telemetry-fallback.ts +2 -0
  319. package/src/core/hooks/use-theme-hooks.ts +16 -0
  320. package/src/core/styles/dark.ts +2 -0
  321. package/src/core/styles/global.ts +96 -116
  322. package/src/core/styles/index.ts +2 -1
  323. package/src/core/styles/search-ai-appear.ts +16 -0
  324. package/src/core/types/code-walkthrough.ts +103 -0
  325. package/src/core/types/consent.ts +51 -0
  326. package/src/core/types/dropdown.ts +1 -0
  327. package/src/core/types/hooks.ts +8 -0
  328. package/src/core/types/index.ts +4 -0
  329. package/src/core/types/l10n.ts +46 -2
  330. package/src/core/types/mcp.ts +4 -2
  331. package/src/core/types/navigation-tabs.ts +54 -0
  332. package/src/core/types/prompt.ts +11 -0
  333. package/src/core/types/search.ts +23 -0
  334. package/src/core/utils/Dynamic.tsx +67 -35
  335. package/src/core/utils/assign-ref.ts +9 -0
  336. package/src/core/utils/consent-settings-link-placement.ts +18 -0
  337. package/src/core/utils/content-segments.ts +98 -6
  338. package/src/core/utils/download-code-walkthrough.ts +1 -1
  339. package/src/core/utils/dropdown.ts +14 -0
  340. package/src/core/utils/get-ai-prompt-link.ts +30 -0
  341. package/src/core/utils/get-code-walkthrough-file-text.ts +1 -1
  342. package/src/core/utils/index.ts +5 -0
  343. package/src/core/utils/match-code-walkthrough-conditions.ts +1 -1
  344. package/src/core/utils/mcp.ts +16 -4
  345. package/src/core/utils/menu.ts +54 -9
  346. package/src/core/utils/navigation-tabs.ts +99 -0
  347. package/src/core/utils/search-ai-activity.ts +110 -0
  348. package/src/core/utils/urls.ts +3 -1
  349. package/src/core/utils/with-load-progress.ts +23 -18
  350. package/src/ext/configure.ts +1 -1
  351. package/src/icons/CheckboxIcon/CheckboxIcon.tsx +1 -0
  352. package/src/icons/ChevronSortIcon/ChevronSortIcon.tsx +1 -0
  353. package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
  354. package/src/icons/LinkIcon/LinkIconSymbol.tsx +51 -0
  355. package/src/index.ts +8 -0
  356. package/src/layouts/PageLayout.tsx +1 -1
  357. package/src/layouts/RootLayout.tsx +2 -2
  358. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +2 -2
  359. package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +4 -4
  360. package/src/markdoc/components/CodeWalkthrough/CodePanelHeader.tsx +1 -1
  361. package/src/markdoc/components/CodeWalkthrough/CodePanelToolbar.tsx +1 -1
  362. package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +1 -1
  363. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +2 -2
  364. package/src/markdoc/components/Prompt/Prompt.tsx +219 -0
  365. package/src/markdoc/components/Prompt/PromptActions.tsx +104 -0
  366. package/src/markdoc/components/Prompt/PromptBody.tsx +107 -0
  367. package/src/markdoc/components/Prompt/PromptCopyButton.tsx +73 -0
  368. package/src/markdoc/components/Prompt/PromptHeader.tsx +81 -0
  369. package/src/markdoc/components/Prompt/PromptShowHideButton.tsx +43 -0
  370. package/src/markdoc/components/Prompt/PromptToggleLabel.tsx +41 -0
  371. package/src/markdoc/components/Prompt/variables.dark.ts +5 -0
  372. package/src/markdoc/components/Prompt/variables.ts +41 -0
  373. package/src/markdoc/components/Tabs/TabList.tsx +9 -12
  374. package/src/markdoc/components/default.ts +1 -0
  375. package/src/markdoc/default.ts +2 -0
  376. package/src/markdoc/tags/code-walkthrough.ts +1 -1
  377. package/src/markdoc/tags/connect-mcp.ts +16 -1
  378. package/src/markdoc/tags/prompt.ts +58 -0
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ import { Button } from '@redocly/theme/components/Button/Button';
7
+ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { resolveSettingsLinkPlacement } from '@redocly/theme/core/utils';
9
+
10
+ export function ConsentSettingsLink(): JSX.Element | null {
11
+ const { useConsent, useTranslate } = useThemeHooks();
12
+ const { consent: consentConfig, footer } = useThemeConfig() || {};
13
+ const consent = useConsent();
14
+ const { translate } = useTranslate();
15
+
16
+ if (!consent.enabled || resolveSettingsLinkPlacement(consentConfig, footer) !== 'footer') {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <SettingsLinkButton
22
+ variant="link"
23
+ size="small"
24
+ data-component-name="Consent/ConsentSettingsLink"
25
+ onClick={consent.openDialog}
26
+ >
27
+ {translate('consent.settingsLink', 'Cookie preferences')}
28
+ </SettingsLinkButton>
29
+ );
30
+ }
31
+
32
+ const SettingsLinkButton = styled(Button)`
33
+ margin: var(--footer-link-padding-vertical) 0 var(--footer-link-padding-vertical) var(--spacing-sm);
34
+ padding: 0;
35
+ color: var(--footer-link-text-color);
36
+ font: inherit;
37
+ line-height: inherit;
38
+
39
+ &:hover,
40
+ &:focus-visible {
41
+ color: var(--footer-link-color-hover);
42
+ background: none;
43
+ }
44
+ `;
@@ -0,0 +1,28 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const consent = css`
4
+ /**
5
+ * @tokens Consent
6
+ */
7
+ --consent-banner-width: 380px;
8
+ --consent-banner-offset: var(--spacing-base); // @presenter Spacing
9
+ --consent-banner-bg-color: var(--bg-color); // @presenter Color
10
+ --consent-banner-text-color: var(--text-color-primary); // @presenter Color
11
+ --consent-banner-border-color: var(--border-color-primary); // @presenter Color
12
+ --consent-banner-border-radius: var(--border-radius-lg);
13
+ --consent-banner-box-shadow: var(--bg-raised-shadow);
14
+ --consent-banner-padding: var(--spacing-base); // @presenter Spacing
15
+ --consent-banner-gap: var(--spacing-sm); // @presenter Spacing
16
+ --consent-banner-title-font-size: var(--font-size-base);
17
+ --consent-banner-font-size: var(--font-size-sm);
18
+ --consent-banner-z-index: var(--z-index-overlay);
19
+
20
+ --consent-dialog-width: 560px;
21
+ --consent-dialog-max-width: calc(100vw - 2 * var(--spacing-base));
22
+ --consent-dialog-padding: var(--spacing-lg); // @presenter Spacing
23
+ --consent-dialog-border-radius: var(--border-radius-lg);
24
+ --consent-dialog-bg-color: var(--modal-bg-color); // @presenter Color
25
+ --consent-dialog-box-shadow: var(--modal-box-shadow);
26
+ --consent-dialog-row-border-color: var(--border-color-primary); // @presenter Color
27
+ --consent-dialog-description-color: var(--text-color-secondary); // @presenter Color
28
+ `;
@@ -11,9 +11,10 @@ import { createPortal } from 'react-dom';
11
11
  import styled, { css } from 'styled-components';
12
12
 
13
13
  import type { CSSProperties, ReactElement } from 'react';
14
- import type { RenderableChildren } from '@redocly/theme/core/types';
14
+ import type { DropdownAlignment, RenderableChildren } from '@redocly/theme/core/types';
15
15
 
16
16
  import { useOutsideClick, useControlledState } from '@redocly/theme/core/hooks';
17
+ import { fitDropdownAlignment } from '@redocly/theme/core/utils';
17
18
  import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
18
19
  import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
19
20
 
@@ -37,7 +38,7 @@ export type DropdownProps = {
37
38
  children: RenderableChildren<DropdownMenuContext>;
38
39
  triggerEvent?: 'click' | 'hover';
39
40
  placement?: 'top' | 'bottom';
40
- alignment?: 'start' | 'end';
41
+ alignment?: DropdownAlignment;
41
42
 
42
43
  active?: boolean;
43
44
  closeOnClick?: boolean;
@@ -46,6 +47,8 @@ export type DropdownProps = {
46
47
  className?: string;
47
48
  withArrow?: boolean;
48
49
  portalled?: boolean;
50
+ /** Element the menu lines up with when it is not the dropdown itself, e.g. a whole split button. */
51
+ alignmentAnchorRef?: React.RefObject<HTMLElement | null>;
49
52
 
50
53
  onClick?: (event: React.UIEvent) => void;
51
54
  onClose?: () => void;
@@ -66,6 +69,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
66
69
  placement,
67
70
  alignment,
68
71
  portalled = false,
72
+ alignmentAnchorRef,
69
73
  onClick,
70
74
  onClose,
71
75
  onOpen,
@@ -76,6 +80,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
76
80
  const menuRef = useRef<HTMLDivElement | null>(null);
77
81
  const [isOpen, setIsOpen] = useControlledState<boolean>(false, active);
78
82
  const [menuStyle, setMenuStyle] = useState<CSSProperties>({});
83
+ const [fittedAlignment, setFittedAlignment] = useState(alignment);
79
84
 
80
85
  const wrapperRef = (ref as React.RefObject<HTMLDivElement | null>) || dropdownRef;
81
86
 
@@ -115,7 +120,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
115
120
  const updateMenuPosition = useCallback(() => {
116
121
  const wrapper = wrapperRef.current;
117
122
  if (!wrapper) return;
118
- const rect = wrapper.getBoundingClientRect();
123
+ const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
119
124
  const menuHeight = menuRef.current?.offsetHeight ?? 0;
120
125
  const fitsBelow = rect.bottom + menuHeight <= window.innerHeight;
121
126
  const fitsAbove = rect.top - menuHeight >= 0;
@@ -126,7 +131,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
126
131
  } else {
127
132
  style.top = rect.bottom;
128
133
  }
129
- if (alignment === 'end') {
134
+ if (fitDropdownAlignment(rect, menuRef.current?.offsetWidth ?? 0, alignment) === 'end') {
130
135
  style.right = window.innerWidth - rect.right;
131
136
  } else {
132
137
  style.left = rect.left;
@@ -136,7 +141,24 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
136
141
  style.zIndex = zIndex;
137
142
  }
138
143
  setMenuStyle(style);
139
- }, [placement, alignment, wrapperRef]);
144
+ }, [placement, alignment, wrapperRef, alignmentAnchorRef]);
145
+
146
+ /* The absolute-positioned menu is placed by CSS, so the side is measured once it is
147
+ shown and again on resize; a side that runs off the viewport flips to the other one. */
148
+ const updateFittedAlignment = useCallback(() => {
149
+ const wrapper = wrapperRef.current;
150
+ const menu = menuRef.current;
151
+ if (!wrapper || !menu) return;
152
+ const rect = (alignmentAnchorRef?.current ?? wrapper).getBoundingClientRect();
153
+ setFittedAlignment(fitDropdownAlignment(rect, menu.offsetWidth, alignment));
154
+ }, [alignment, wrapperRef, alignmentAnchorRef]);
155
+
156
+ useIsomorphicLayoutEffect(() => {
157
+ if (portalled || !isOpen) return;
158
+ updateFittedAlignment();
159
+ window.addEventListener('resize', updateFittedAlignment);
160
+ return () => window.removeEventListener('resize', updateFittedAlignment);
161
+ }, [portalled, isOpen, updateFittedAlignment]);
140
162
 
141
163
  useIsomorphicLayoutEffect(() => {
142
164
  if (!portalled || !isOpen) return;
@@ -156,20 +178,23 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
156
178
 
157
179
  const triggerChild = React.Children.only(trigger) as ReactElement<TriggerProps>;
158
180
 
181
+ const isLinkTrigger = Boolean(triggerChild.props?.to || triggerChild.props?.href);
182
+
159
183
  const dropdownTrigger = cloneElement(triggerChild, {
160
184
  onClick: triggerEvent === 'click' ? handleToggle : undefined,
185
+ 'aria-haspopup': 'menu',
161
186
  'aria-expanded': isOpen,
162
187
  icon: withArrow ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined,
163
188
  ...(withArrow ? { iconPosition: 'right' } : {}),
164
189
  ...triggerChild.props,
165
- onKeyDown: triggerEvent === 'click' ? handleKeyDown : undefined,
190
+ onKeyDown: triggerEvent === 'click' || !isLinkTrigger ? handleKeyDown : undefined,
166
191
  });
167
192
 
168
193
  const menu = (
169
194
  <ChildrenWrapper
170
195
  ref={menuRef}
171
196
  $placement={placement}
172
- $alignment={alignment}
197
+ $alignment={fittedAlignment}
173
198
  $isOpen={isOpen}
174
199
  $portalled={portalled}
175
200
  style={portalled ? menuStyle : undefined}
@@ -6,7 +6,12 @@ import type { DropdownMenuItemProps } from '@redocly/theme/components/Dropdown/D
6
6
 
7
7
  import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
8
8
 
9
- export type DropdownMenuProps = { className?: string; role?: string; footer?: React.ReactNode } & (
9
+ export type DropdownMenuProps = {
10
+ id?: string;
11
+ className?: string;
12
+ role?: string;
13
+ footer?: React.ReactNode;
14
+ } & (
10
15
  | {
11
16
  items: DropdownMenuItemProps[];
12
17
  }
@@ -32,6 +37,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
32
37
  <DropdownMenuWrapper
33
38
  data-component-name="Dropdown/DropdownMenu"
34
39
  data-testid="dropdown-menu"
40
+ id={props.id}
35
41
  className={props.className}
36
42
  role={props.role || 'menu'}
37
43
  >
@@ -18,6 +18,7 @@ export type ReasonsProps = {
18
18
  export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.Element | null {
19
19
  const { label, component = 'checkbox', items = [] } = settings;
20
20
  const [checkedState, setCheckedState] = React.useState(new Array(items.length).fill(false));
21
+ const idPrefix = React.useId();
21
22
  const { useTranslate } = useThemeHooks();
22
23
  const { translate } = useTranslate();
23
24
 
@@ -51,6 +52,7 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
51
52
  {items.map((reason, idx) => (
52
53
  <OptionWrapper key={reason}>
53
54
  <input
55
+ id={`${idPrefix}-${idx}`}
54
56
  type={component}
55
57
  value={reason}
56
58
  checked={checkedState[idx]}
@@ -59,8 +61,7 @@ export function Reasons({ settings, onChange, className }: ReasonsProps): JSX.El
59
61
  />
60
62
  <label
61
63
  data-translation-key={`feedback.settings.reasons.items.${idx + 1}`}
62
- id={reason}
63
- onClick={() => handleOptionChange(idx)}
64
+ htmlFor={`${idPrefix}-${idx}`}
64
65
  >
65
66
  {translate(`feedback.settings.reasons.items.${idx + 1}`, reason)}
66
67
  </label>
@@ -30,26 +30,24 @@ export function FilterCheckboxes({
30
30
  <FilterOptions>
31
31
  {(filter.filteredOptions || filter.options).map(({ value, count }) => {
32
32
  const id = 'filter--' + filter.property + '--' + value;
33
+ const checked =
34
+ filter.selectedOptions instanceof Set
35
+ ? filter.selectedOptions.has(value) ||
36
+ filter.selectedOptions.has(value?.toLowerCase())
37
+ : false;
33
38
  return (
34
- <FilterCheckboxOption
35
- key={id}
36
- role="link"
37
- onClick={() => {
38
- filter.toggleOption(value);
39
- telemetry.sendFilterCheckboxToggledMessage([
40
- getBaseDataAttributes('filterId', 'checkbox'),
41
- ]);
42
- }}
43
- >
44
- <CheckboxIcon
45
- checked={
46
- filter.selectedOptions instanceof Set
47
- ? filter.selectedOptions.has(value) ||
48
- filter.selectedOptions.has(value?.toLowerCase())
49
- : false
50
- }
51
- />
39
+ <FilterCheckboxOption key={id}>
40
+ <CheckboxIcon checked={checked} />
52
41
  <FilterOptionLabel data-translation-key={value}>
42
+ <FilterCheckboxInput
43
+ checked={checked}
44
+ onChange={() => {
45
+ filter.toggleOption(value);
46
+ telemetry.sendFilterCheckboxToggledMessage([
47
+ getBaseDataAttributes('filterId', 'checkbox'),
48
+ ]);
49
+ }}
50
+ />
53
51
  {changeTextCasing(translate(value), filterValuesCasing)}
54
52
  </FilterOptionLabel>
55
53
  {showCounter && <CounterTag borderless>{count}</CounterTag>}
@@ -63,6 +61,23 @@ export function FilterCheckboxes({
63
61
 
64
62
  const FilterCheckboxOption = styled(FilterOption)`
65
63
  padding-left: var(--filter-option-checkbox-padding-left);
64
+
65
+ &:has(input:focus-visible) {
66
+ outline: 1px solid var(--button-border-color-focused);
67
+ outline-offset: -1px;
68
+ }
69
+ `;
70
+
71
+ const FilterCheckboxInput = styled.input.attrs({ type: 'checkbox' })`
72
+ position: absolute;
73
+ width: 1px;
74
+ height: 1px;
75
+ margin: -1px;
76
+ padding: 0;
77
+ overflow: hidden;
78
+ clip: rect(0 0 0 0);
79
+ white-space: nowrap;
80
+ border: 0;
66
81
  `;
67
82
 
68
83
  const FilterCheckboxesWrapper = styled.div`
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -11,6 +11,7 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
11
11
  import { formatDateWithoutTimeZone } from '@redocly/theme/core/utils';
12
12
 
13
13
  export function FilterDateRange({ filter }: FilterProps): JSX.Element {
14
+ const [today] = useState(() => new Date());
14
15
  const { useTranslate } = useThemeHooks();
15
16
  const { translate } = useTranslate();
16
17
 
@@ -33,7 +34,7 @@ export function FilterDateRange({ filter }: FilterProps): JSX.Element {
33
34
  monthPlaceholder="MM"
34
35
  yearPlaceholder="YYYY"
35
36
  value={from}
36
- maxDate={new Date()}
37
+ maxDate={today}
37
38
  onChange={(from) => {
38
39
  if (Array.isArray(from)) return;
39
40
  filter.selectOption({
@@ -9,6 +9,7 @@ import { breakpoints, isEmptyArray } from '@redocly/theme/core/utils';
9
9
  import { useThemeConfig } from '@redocly/theme/core/hooks';
10
10
  import { FooterCopyright } from '@redocly/theme/components/Footer/FooterCopyright';
11
11
  import { FooterColumn } from '@redocly/theme/components/Footer/FooterColumn';
12
+ import { ConsentSettingsLink } from '@redocly/theme/components/Consent/ConsentSettingsLink';
12
13
 
13
14
  export type FooterProps = {
14
15
  className?: string;
@@ -43,6 +44,7 @@ export function Footer({ className }: FooterProps): JSX.Element | null {
43
44
  )}
44
45
  <FooterCopyrightWrapper>
45
46
  {copyrightText && <FooterCopyright copyrightText={copyrightText} />}
47
+ <ConsentSettingsLink />
46
48
  </FooterCopyrightWrapper>
47
49
  </FooterWrapper>
48
50
  );
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
 
4
- import type { KeyboardEvent, JSX } from 'react';
4
+ import type { KeyboardEvent, JSX, MouseEvent } from 'react';
5
5
 
6
6
  import { parseSrcSet, parseStyleString } from '@redocly/theme/core/utils';
7
7
  import { useModalScrollLock } from '@redocly/theme/core/hooks';
@@ -42,6 +42,7 @@ export function Image(props: ImageProps): JSX.Element {
42
42
  } = props;
43
43
 
44
44
  const lightboxContainerRef = useRef<HTMLDivElement>(null);
45
+ const lightboxTriggerRef = useRef<HTMLElement | null>(null);
45
46
  const [lightboxImage, setLightboxImage] = useState<string | undefined>(undefined);
46
47
  const parsedSourceSetMap = useMemo(() => {
47
48
  return srcSet ? parseSrcSet(srcSet) : new Map();
@@ -55,15 +56,32 @@ export function Image(props: ImageProps): JSX.Element {
55
56
  }, []);
56
57
 
57
58
  const handleImageClick = useCallback(
58
- (src: string) => {
59
+ (src: string, trigger: HTMLElement) => {
59
60
  if (!withLightbox || lightboxImage) {
60
61
  return;
61
62
  }
63
+ lightboxTriggerRef.current = trigger;
62
64
  setLightboxImage(src);
63
65
  },
64
66
  [withLightbox, lightboxImage],
65
67
  );
66
68
 
69
+ const getLightboxTriggerProps = (imageSrc: string) =>
70
+ withLightbox
71
+ ? {
72
+ role: 'button',
73
+ tabIndex: 0,
74
+ 'aria-label': alt ? undefined : 'Open image',
75
+ onClick: (e: MouseEvent<HTMLElement>) => handleImageClick(imageSrc, e.currentTarget),
76
+ onKeyDown: (e: KeyboardEvent<HTMLElement>) => {
77
+ if (e.key === 'Enter' || e.key === ' ') {
78
+ e.preventDefault();
79
+ handleImageClick(imageSrc, e.currentTarget);
80
+ }
81
+ },
82
+ }
83
+ : {};
84
+
67
85
  const handleCloseLightbox = useCallback(() => {
68
86
  setLightboxImage(undefined);
69
87
  }, []);
@@ -72,6 +90,9 @@ export function Image(props: ImageProps): JSX.Element {
72
90
  useEffect(() => {
73
91
  if (lightboxImage) {
74
92
  lightboxContainerRef.current?.focus();
93
+ } else {
94
+ lightboxTriggerRef.current?.focus();
95
+ lightboxTriggerRef.current = null;
75
96
  }
76
97
  }, [lightboxImage]);
77
98
 
@@ -92,7 +113,7 @@ export function Image(props: ImageProps): JSX.Element {
92
113
  width={width}
93
114
  height={height}
94
115
  style={combinedStyles}
95
- onClick={() => handleImageClick(src)}
116
+ {...getLightboxTriggerProps(src)}
96
117
  />
97
118
  ) : (
98
119
  Array.from(parsedSourceSetMap).map(([key, value]) => (
@@ -106,7 +127,7 @@ export function Image(props: ImageProps): JSX.Element {
106
127
  height={height}
107
128
  $withLightbox={withLightbox}
108
129
  style={combinedStyles}
109
- onClick={() => handleImageClick(value)}
130
+ {...getLightboxTriggerProps(value)}
110
131
  />
111
132
  ))
112
133
  );
@@ -122,7 +143,7 @@ export function Image(props: ImageProps): JSX.Element {
122
143
  src={galleryImageSrc}
123
144
  alt={alt}
124
145
  style={withLightbox ? { cursor: 'pointer' } : undefined}
125
- onClick={() => handleImageClick(galleryImageSrc)}
146
+ {...getLightboxTriggerProps(galleryImageSrc)}
126
147
  />
127
148
  ))}
128
149
  </GalleryRow>
@@ -7,40 +7,50 @@ import { Button } from '@redocly/theme/components/Button/Button';
7
7
  import { ShowMoreToggleButton } from '@redocly/theme/components/Button/ShowMoreToggleButton';
8
8
  import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
9
9
 
10
+ export type JsonFold = {
11
+ isExpanded: (path: string, level: number) => boolean;
12
+ setExpanded: (path: string, expanded: boolean) => void;
13
+ };
14
+
15
+ type Foldable = [path: string, level: number];
16
+
17
+ export function isFoldable(value: unknown): value is Record<string, unknown> {
18
+ return (
19
+ typeof value === 'object' &&
20
+ value !== null &&
21
+ Object.values(value).some((item) => item !== undefined)
22
+ );
23
+ }
24
+
25
+ export function collectFoldables(value: unknown, path = '', level = 1): Foldable[] {
26
+ if (!isFoldable(value)) return [];
27
+ return Object.entries(value).flatMap(([key, child]): Foldable[] => {
28
+ const childPath = `${path}/${key}`;
29
+ return isFoldable(child)
30
+ ? [[childPath, level], ...collectFoldables(child, childPath, level + 1)]
31
+ : [];
32
+ });
33
+ }
34
+
10
35
  export function JsonValue(props: {
11
36
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
12
37
  value: any;
13
38
  level: number;
39
+ path: string;
14
40
  standalone?: boolean; // if true, will render the opening and closing punctuation (when it's an item inside array or top-level)
15
- defaultExpandLevel: number;
16
- expandAllSignal: boolean | undefined;
41
+ fold: JsonFold;
17
42
  maxStringLength?: number;
18
43
  }): React.ReactNode {
19
- const { standalone, defaultExpandLevel, level, value, expandAllSignal, maxStringLength } = props;
44
+ const { standalone, fold, level, path, value, maxStringLength } = props;
20
45
  const indent = ' '.repeat(level * 2);
21
-
22
- const [isExpandedState, setExpanded] = React.useState(
23
- expandAllSignal ?? level < defaultExpandLevel,
24
- );
25
- const isExpanded = expandAllSignal !== undefined && level > 0 ? expandAllSignal : isExpandedState;
26
- const [prevExpandAllSignal, setPrevExpandAllSignal] = React.useState(expandAllSignal);
27
- const [prevLevel, setPrevLevel] = React.useState(level);
28
-
29
- if (prevExpandAllSignal !== expandAllSignal || prevLevel !== level) {
30
- setPrevExpandAllSignal(expandAllSignal);
31
- setPrevLevel(level);
32
- if (expandAllSignal !== undefined && !(expandAllSignal === false && level === 0)) {
33
- setExpanded(expandAllSignal);
34
- }
35
- }
46
+ const isExpanded = fold.isExpanded(path, level);
36
47
 
37
48
  const keys: string[] =
38
49
  (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties
39
50
 
40
51
  const valueType = typeof value;
41
- const isPrimitive = (valueType !== 'object' && value !== null) || keys.length === 0;
42
52
 
43
- if (isPrimitive) {
53
+ if (!isFoldable(value)) {
44
54
  const isOversizedString =
45
55
  typeof value === 'string' && maxStringLength !== undefined && value.length > maxStringLength;
46
56
  return (
@@ -64,12 +74,12 @@ export function JsonValue(props: {
64
74
  <JsonValue
65
75
  value={item}
66
76
  level={level + 1}
77
+ path={`${path}/${index}`}
67
78
  standalone={true}
68
- defaultExpandLevel={defaultExpandLevel}
69
- expandAllSignal={expandAllSignal}
79
+ fold={fold}
70
80
  maxStringLength={maxStringLength}
71
81
  />
72
- {index < value.length - 1 ? ',\n' : null}
82
+ {index < value.length - 1 ? <span>{',\n'}</span> : null}
73
83
  </React.Fragment>
74
84
  ))
75
85
  : keys.map((key, index) => (
@@ -78,11 +88,11 @@ export function JsonValue(props: {
78
88
  name={key}
79
89
  value={value[key]}
80
90
  level={level + 1}
81
- defaultExpandLevel={defaultExpandLevel}
82
- expandAllSignal={expandAllSignal}
91
+ path={`${path}/${key}`}
92
+ fold={fold}
83
93
  maxStringLength={maxStringLength}
84
94
  />
85
- {index < keys.length - 1 ? ',\n' : null}
95
+ {index < keys.length - 1 ? <span>{',\n'}</span> : null}
86
96
  </React.Fragment>
87
97
  ));
88
98
 
@@ -90,7 +100,7 @@ export function JsonValue(props: {
90
100
  <>
91
101
  {indent}
92
102
  {level > 0 ? (
93
- <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
103
+ <ExpandIcon onClick={() => fold.setExpanded(path, !isExpanded)} isExpanded={isExpanded} />
94
104
  ) : null}
95
105
  {openingPunctuation}
96
106
  {isExpanded ? '\n' : null}
@@ -119,33 +129,18 @@ function JsonKeyValue(props: {
119
129
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
120
130
  value: any;
121
131
  level: number;
122
- defaultExpandLevel: number;
123
- expandAllSignal: boolean | undefined;
132
+ path: string;
133
+ fold: JsonFold;
124
134
  maxStringLength?: number;
125
135
  }): React.ReactNode {
126
- const { defaultExpandLevel, level, name, value, expandAllSignal, maxStringLength } = props;
136
+ const { fold, level, name, path, value, maxStringLength } = props;
127
137
  const indent = ' '.repeat(level * 2);
128
- const keys: string[] =
129
- (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties
130
-
131
- const isCollapsible = typeof value === 'object' && value !== null && keys.length > 0;
132
- const [isExpandedState, setExpanded] = React.useState(
133
- expandAllSignal ?? level < defaultExpandLevel,
134
- );
135
- const isExpanded = expandAllSignal !== undefined ? expandAllSignal : isExpandedState;
136
- const [prevExpandAllSignal, setPrevExpandAllSignal] = React.useState(expandAllSignal);
137
-
138
- if (prevExpandAllSignal !== expandAllSignal) {
139
- setPrevExpandAllSignal(expandAllSignal);
140
- if (expandAllSignal !== undefined) {
141
- setExpanded(expandAllSignal);
142
- }
143
- }
138
+ const isExpanded = fold.isExpanded(path, level);
144
139
 
145
140
  const openingPunctuation = Array.isArray(value) ? '[' : '{';
146
141
  const closingPunctuation = Array.isArray(value) ? ']' : '}';
147
142
 
148
- const valueElement = isCollapsible ? (
143
+ const valueElement = isFoldable(value) ? (
149
144
  <>
150
145
  {openingPunctuation}
151
146
  {isExpanded ? (
@@ -154,8 +149,8 @@ function JsonKeyValue(props: {
154
149
  <JsonValue
155
150
  value={value}
156
151
  level={level}
157
- defaultExpandLevel={defaultExpandLevel}
158
- expandAllSignal={expandAllSignal}
152
+ path={path}
153
+ fold={fold}
159
154
  maxStringLength={maxStringLength}
160
155
  />
161
156
  {'\n'}
@@ -170,8 +165,8 @@ function JsonKeyValue(props: {
170
165
  <JsonValue
171
166
  value={value}
172
167
  level={level}
173
- defaultExpandLevel={defaultExpandLevel}
174
- expandAllSignal={expandAllSignal}
168
+ path={path}
169
+ fold={fold}
175
170
  maxStringLength={maxStringLength}
176
171
  />
177
172
  );
@@ -179,10 +174,12 @@ function JsonKeyValue(props: {
179
174
  return (
180
175
  <>
181
176
  {indent}
182
- {isCollapsible ? (
183
- <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
177
+ {isFoldable(value) ? (
178
+ <ExpandIcon onClick={() => fold.setExpanded(path, !isExpanded)} isExpanded={isExpanded} />
184
179
  ) : null}
185
- <span className="property token string">{`"${name}"`}</span>: {valueElement}
180
+ <span className="property token string">{`"${name}"`}</span>
181
+ <span>{': '}</span>
182
+ {valueElement}
186
183
  </>
187
184
  );
188
185
  }