@redocly/theme 0.69.0-next.1 → 0.69.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 (280) hide show
  1. package/lib/components/Accordion/Accordion.js +5 -1
  2. package/lib/components/Accordion/variables.js +1 -1
  3. package/lib/components/Admonition/Admonition.d.ts +1 -1
  4. package/lib/components/Admonition/Admonition.js +1 -1
  5. package/lib/components/Admonition/variables.dark.js +2 -2
  6. package/lib/components/Admonition/variables.js +10 -10
  7. package/lib/components/Badge/Badge.d.ts +3 -1
  8. package/lib/components/Badge/Badge.js +7 -2
  9. package/lib/components/Banner/variables.dark.js +2 -0
  10. package/lib/components/Banner/variables.js +3 -1
  11. package/lib/components/Breadcrumbs/Breadcrumb.js +1 -1
  12. package/lib/components/Breadcrumbs/variables.js +1 -1
  13. package/lib/components/Button/ButtonGroup.d.ts +3 -5
  14. package/lib/components/Button/ButtonGroup.js +25 -5
  15. package/lib/components/Button/variables.js +27 -4
  16. package/lib/components/Buttons/ConnectMCPButton.js +39 -123
  17. package/lib/components/Buttons/CopyButton.js +12 -1
  18. package/lib/components/Buttons/variables.d.ts +0 -1
  19. package/lib/components/Buttons/variables.js +3 -44
  20. package/lib/components/Catalog/CatalogEntities.js +1 -0
  21. package/lib/components/Catalog/CatalogEntity/CatalogEntityLinks.js +1 -1
  22. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +1 -1
  23. package/lib/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.js +1 -1
  24. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +5 -3
  25. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
  26. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +1 -0
  27. package/lib/components/Catalog/CatalogViewModeToggle.d.ts +1 -1
  28. package/lib/components/Catalog/CatalogViewModeToggle.js +2 -43
  29. package/lib/components/Catalog/variables.js +5 -5
  30. package/lib/components/CatalogClassic/variables.js +2 -2
  31. package/lib/components/CodeBlock/CodeBlock.js +2 -1
  32. package/lib/components/CodeBlock/CodeBlockContainer.js +1 -0
  33. package/lib/components/Dropdown/Dropdown.d.ts +10 -20
  34. package/lib/components/Dropdown/Dropdown.js +13 -2
  35. package/lib/components/Dropdown/DropdownMenuItemContent.d.ts +9 -0
  36. package/lib/components/Dropdown/DropdownMenuItemContent.js +56 -0
  37. package/lib/components/Dropdown/variables.js +19 -2
  38. package/lib/components/Feedback/Mood.js +18 -14
  39. package/lib/components/Feedback/Rating.js +18 -13
  40. package/lib/components/Feedback/Scale.js +27 -22
  41. package/lib/components/Feedback/Sentiment.js +17 -13
  42. package/lib/components/Filter/FilterInput.js +4 -2
  43. package/lib/components/Filter/FilterSelect.js +1 -0
  44. package/lib/components/Filter/variables.js +2 -2
  45. package/lib/components/Image/variables.js +5 -3
  46. package/lib/components/JsonViewer/Helpers.js +12 -42
  47. package/lib/components/LanguagePicker/variables.js +1 -1
  48. package/lib/components/MCP/McpConnectionTabs.d.ts +7 -0
  49. package/lib/components/MCP/McpConnectionTabs.js +161 -0
  50. package/lib/components/Markdown/styles/base-table.js +8 -0
  51. package/lib/components/Markdown/variables.js +1 -1
  52. package/lib/components/Menu/MenuItem.js +1 -1
  53. package/lib/components/Menu/variables.js +1 -1
  54. package/lib/components/Navbar/variables.js +1 -1
  55. package/lib/components/PageActions/PageActions.js +4 -6
  56. package/lib/components/PageActions/PageActionsMenuItem.d.ts +4 -4
  57. package/lib/components/PageActions/PageActionsMenuItem.js +27 -43
  58. package/lib/components/Panel/Panel.js +5 -1
  59. package/lib/components/Panel/variables.js +1 -1
  60. package/lib/components/Product/variables.js +2 -1
  61. package/lib/components/ReplayGate/ReplayGate.d.ts +1 -0
  62. package/lib/components/Search/FilterFields/SearchFilterFieldSelect.js +22 -32
  63. package/lib/components/Search/SearchAiDialog.js +4 -1
  64. package/lib/components/Search/SearchAiMessage.js +1 -1
  65. package/lib/components/Search/SearchAiNegativeFeedbackForm.js +2 -2
  66. package/lib/components/Search/SearchDialog.js +2 -2
  67. package/lib/components/Search/SearchItem.js +6 -3
  68. package/lib/components/Search/SearchRecent.js +1 -0
  69. package/lib/components/Search/SearchSuggestedPages.js +1 -0
  70. package/lib/components/Search/variables.js +9 -5
  71. package/lib/components/Segmented/Segmented.js +3 -3
  72. package/lib/components/Select/Select.js +30 -23
  73. package/lib/components/Select/variables.js +3 -3
  74. package/lib/components/Sidebar/Sidebar.js +2 -38
  75. package/lib/components/SplitButton/SplitButton.d.ts +21 -0
  76. package/lib/components/SplitButton/SplitButton.js +37 -0
  77. package/lib/components/Switch/variables.dark.js +2 -2
  78. package/lib/components/Switch/variables.js +2 -2
  79. package/lib/components/Tag/variables.js +2 -2
  80. package/lib/components/Tags/variables.js +1 -1
  81. package/lib/components/Toast/variables.js +1 -1
  82. package/lib/components/Tooltip/AnchorTooltip.js +2 -2
  83. package/lib/components/Tooltip/JsTooltip.js +2 -2
  84. package/lib/components/UserMenu/variables.js +1 -1
  85. package/lib/core/constants/mcp.d.ts +4 -0
  86. package/lib/core/constants/mcp.js +10 -1
  87. package/lib/core/constants/search.js +2 -2
  88. package/lib/core/contexts/AccordionGroupContext.d.ts +1 -0
  89. package/lib/core/contexts/AccordionGroupContext.js +6 -0
  90. package/lib/core/contexts/PageActionsContext.d.ts +5 -0
  91. package/lib/core/contexts/PageActionsContext.js +10 -0
  92. package/lib/core/contexts/index.d.ts +2 -0
  93. package/lib/core/contexts/index.js +2 -0
  94. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +5 -17
  95. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +2 -0
  96. package/lib/core/hooks/menu/use-menu-item-expanded.js +1 -0
  97. package/lib/core/hooks/use-active-heading.js +63 -15
  98. package/lib/core/hooks/use-active-section-id.js +3 -1
  99. package/lib/core/hooks/use-connect-mcp-button.d.ts +1 -1
  100. package/lib/core/hooks/use-connect-mcp-button.js +23 -15
  101. package/lib/core/hooks/use-control.js +3 -0
  102. package/lib/core/hooks/use-local-state.js +1 -0
  103. package/lib/core/hooks/use-mcp-config.d.ts +2 -0
  104. package/lib/core/hooks/use-mcp-config.js +14 -4
  105. package/lib/core/hooks/use-mobile-menu.js +10 -2
  106. package/lib/core/hooks/use-tabs.js +4 -2
  107. package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
  108. package/lib/core/hooks/use-telemetry-fallback.js +2 -0
  109. package/lib/core/styles/global.js +16 -11
  110. package/lib/core/styles/palette.js +2 -0
  111. package/lib/core/types/button-group.d.ts +2 -0
  112. package/lib/core/types/button-group.js +3 -0
  113. package/lib/core/types/catalog.d.ts +2 -4
  114. package/lib/core/types/common.d.ts +2 -0
  115. package/lib/core/types/hooks.d.ts +1 -0
  116. package/lib/core/types/index.d.ts +1 -0
  117. package/lib/core/types/l10n.d.ts +1 -1
  118. package/lib/core/types/mcp.d.ts +1 -1
  119. package/lib/core/utils/RenderWithLink.d.ts +2 -0
  120. package/lib/core/utils/RenderWithLink.js +20 -0
  121. package/lib/core/utils/index.d.ts +1 -0
  122. package/lib/core/utils/index.js +1 -0
  123. package/lib/core/utils/mcp.d.ts +5 -0
  124. package/lib/core/utils/mcp.js +35 -0
  125. package/lib/icons/CodexIcon/CodexIcon.d.ts +25 -0
  126. package/lib/icons/CodexIcon/CodexIcon.js +22 -0
  127. package/lib/icons/LinkIcon/LinkIcon.js +1 -1
  128. package/lib/index.d.ts +2 -0
  129. package/lib/index.js +2 -0
  130. package/lib/layouts/McpNotAllowed.js +10 -10
  131. package/lib/markdoc/components/Accordion/Accordion.js +6 -6
  132. package/lib/markdoc/components/Accordion/AccordionGroup.d.ts +2 -1
  133. package/lib/markdoc/components/Accordion/AccordionGroup.js +39 -3
  134. package/lib/markdoc/components/Cards/Card.d.ts +8 -1
  135. package/lib/markdoc/components/Cards/Card.js +55 -16
  136. package/lib/markdoc/components/Cards/CardBadge.d.ts +9 -0
  137. package/lib/markdoc/components/Cards/CardBadge.js +30 -0
  138. package/lib/markdoc/components/Cards/CardIcon.d.ts +5 -4
  139. package/lib/markdoc/components/Cards/CardIcon.js +6 -3
  140. package/lib/markdoc/components/Cards/variables.dark.js +32 -0
  141. package/lib/markdoc/components/Cards/variables.js +81 -7
  142. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +1 -0
  143. package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +1 -1
  144. package/lib/markdoc/components/CodeWalkthrough/variables.js +2 -1
  145. package/lib/markdoc/components/Heading/Heading.js +5 -2
  146. package/lib/markdoc/components/Tabs/TabList.js +6 -2
  147. package/lib/markdoc/components/Tabs/Tabs.js +4 -2
  148. package/lib/markdoc/components/Tabs/variables.js +4 -4
  149. package/lib/markdoc/tags/accordion-group.js +3 -0
  150. package/lib/markdoc/tags/admonition.js +1 -1
  151. package/lib/markdoc/tags/card.js +14 -0
  152. package/lib/markdoc/tags/connect-mcp.js +2 -1
  153. package/package.json +19 -10
  154. package/src/components/Accordion/Accordion.tsx +7 -3
  155. package/src/components/Accordion/variables.ts +1 -1
  156. package/src/components/Admonition/Admonition.tsx +2 -2
  157. package/src/components/Admonition/variables.dark.ts +2 -2
  158. package/src/components/Admonition/variables.ts +10 -10
  159. package/src/components/Badge/Badge.tsx +16 -2
  160. package/src/components/Banner/variables.dark.ts +2 -0
  161. package/src/components/Banner/variables.ts +3 -1
  162. package/src/components/Breadcrumbs/Breadcrumb.tsx +1 -1
  163. package/src/components/Breadcrumbs/variables.ts +1 -1
  164. package/src/components/Button/ButtonGroup.tsx +28 -10
  165. package/src/components/Button/variables.ts +27 -4
  166. package/src/components/Buttons/ConnectMCPButton.tsx +88 -145
  167. package/src/components/Buttons/CopyButton.tsx +14 -2
  168. package/src/components/Buttons/variables.ts +2 -44
  169. package/src/components/Catalog/CatalogEntities.tsx +1 -0
  170. package/src/components/Catalog/CatalogEntity/CatalogEntityLinks.tsx +1 -1
  171. package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +1 -1
  172. package/src/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.tsx +1 -1
  173. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +6 -4
  174. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
  175. package/src/components/Catalog/CatalogFilter/CatalogFilterSelect.tsx +1 -0
  176. package/src/components/Catalog/CatalogViewModeToggle.tsx +2 -17
  177. package/src/components/Catalog/variables.ts +5 -5
  178. package/src/components/CatalogClassic/variables.ts +2 -2
  179. package/src/components/CodeBlock/CodeBlock.tsx +2 -1
  180. package/src/components/CodeBlock/CodeBlockContainer.tsx +1 -0
  181. package/src/components/Dropdown/Dropdown.tsx +25 -4
  182. package/src/components/Dropdown/DropdownMenuItemContent.tsx +72 -0
  183. package/src/components/Dropdown/variables.ts +19 -2
  184. package/src/components/Feedback/Mood.tsx +22 -15
  185. package/src/components/Feedback/Rating.tsx +22 -14
  186. package/src/components/Feedback/Scale.tsx +40 -32
  187. package/src/components/Feedback/Sentiment.tsx +21 -14
  188. package/src/components/Filter/FilterInput.tsx +5 -3
  189. package/src/components/Filter/FilterSelect.tsx +1 -0
  190. package/src/components/Filter/variables.ts +2 -2
  191. package/src/components/Image/variables.ts +5 -3
  192. package/src/components/JsonViewer/Helpers.tsx +13 -9
  193. package/src/components/LanguagePicker/variables.ts +1 -1
  194. package/src/components/MCP/McpConnectionTabs.tsx +285 -0
  195. package/src/components/Markdown/styles/base-table.ts +8 -0
  196. package/src/components/Markdown/variables.ts +1 -1
  197. package/src/components/Menu/MenuItem.tsx +2 -2
  198. package/src/components/Menu/variables.ts +1 -1
  199. package/src/components/Navbar/variables.ts +1 -1
  200. package/src/components/PageActions/PageActions.tsx +22 -23
  201. package/src/components/PageActions/PageActionsMenuItem.tsx +41 -55
  202. package/src/components/Panel/Panel.tsx +7 -3
  203. package/src/components/Panel/variables.ts +1 -1
  204. package/src/components/Product/variables.ts +2 -1
  205. package/src/components/ReplayGate/ReplayGate.tsx +3 -0
  206. package/src/components/Search/FilterFields/SearchFilterFieldSelect.tsx +32 -40
  207. package/src/components/Search/SearchAiDialog.tsx +4 -1
  208. package/src/components/Search/SearchAiMessage.tsx +1 -1
  209. package/src/components/Search/SearchAiNegativeFeedbackForm.tsx +2 -2
  210. package/src/components/Search/SearchDialog.tsx +2 -2
  211. package/src/components/Search/SearchItem.tsx +7 -1
  212. package/src/components/Search/SearchRecent.tsx +1 -0
  213. package/src/components/Search/SearchSuggestedPages.tsx +1 -0
  214. package/src/components/Search/variables.ts +9 -5
  215. package/src/components/Segmented/Segmented.tsx +6 -3
  216. package/src/components/Select/Select.tsx +36 -22
  217. package/src/components/Select/variables.ts +3 -3
  218. package/src/components/Sidebar/Sidebar.tsx +2 -6
  219. package/src/components/SplitButton/SplitButton.tsx +95 -0
  220. package/src/components/Switch/variables.dark.ts +2 -2
  221. package/src/components/Switch/variables.ts +2 -2
  222. package/src/components/Tag/variables.ts +2 -2
  223. package/src/components/Tags/variables.ts +1 -1
  224. package/src/components/Toast/variables.ts +1 -1
  225. package/src/components/Tooltip/AnchorTooltip.tsx +2 -2
  226. package/src/components/Tooltip/JsTooltip.tsx +2 -2
  227. package/src/components/UserMenu/variables.ts +1 -1
  228. package/src/core/constants/mcp.ts +14 -0
  229. package/src/core/constants/search.ts +2 -2
  230. package/src/core/contexts/AccordionGroupContext.tsx +3 -0
  231. package/src/core/contexts/PageActionsContext.tsx +7 -0
  232. package/src/core/contexts/index.ts +2 -0
  233. package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
  234. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +6 -20
  235. package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +2 -0
  236. package/src/core/hooks/menu/use-menu-item-expanded.ts +1 -0
  237. package/src/core/hooks/use-active-heading.ts +83 -15
  238. package/src/core/hooks/use-active-section-id.ts +3 -1
  239. package/src/core/hooks/use-connect-mcp-button.ts +31 -17
  240. package/src/core/hooks/use-control.ts +5 -1
  241. package/src/core/hooks/use-local-state.ts +1 -0
  242. package/src/core/hooks/use-mcp-config.ts +15 -4
  243. package/src/core/hooks/use-mobile-menu.ts +11 -3
  244. package/src/core/hooks/use-tabs.ts +4 -2
  245. package/src/core/hooks/use-telemetry-fallback.ts +2 -0
  246. package/src/core/openapi/index.ts +1 -1
  247. package/src/core/styles/global.ts +17 -12
  248. package/src/core/styles/palette.ts +2 -0
  249. package/src/core/types/button-group.ts +2 -0
  250. package/src/core/types/catalog.ts +2 -4
  251. package/src/core/types/common.ts +3 -0
  252. package/src/core/types/hooks.ts +1 -0
  253. package/src/core/types/index.ts +1 -0
  254. package/src/core/types/l10n.ts +29 -1
  255. package/src/core/types/mcp.ts +1 -1
  256. package/src/core/utils/RenderWithLink.tsx +19 -0
  257. package/src/core/utils/index.ts +1 -0
  258. package/src/core/utils/mcp.ts +39 -0
  259. package/src/icons/CodexIcon/CodexIcon.tsx +23 -0
  260. package/src/icons/LinkIcon/LinkIcon.tsx +1 -1
  261. package/src/index.ts +2 -0
  262. package/src/layouts/McpNotAllowed.tsx +37 -11
  263. package/src/markdoc/components/Accordion/Accordion.tsx +8 -2
  264. package/src/markdoc/components/Accordion/AccordionGroup.tsx +9 -2
  265. package/src/markdoc/components/Cards/Card.tsx +79 -14
  266. package/src/markdoc/components/Cards/CardBadge.tsx +39 -0
  267. package/src/markdoc/components/Cards/CardIcon.tsx +16 -5
  268. package/src/markdoc/components/Cards/variables.dark.ts +32 -0
  269. package/src/markdoc/components/Cards/variables.ts +81 -7
  270. package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +1 -0
  271. package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +1 -1
  272. package/src/markdoc/components/CodeWalkthrough/variables.ts +2 -1
  273. package/src/markdoc/components/Heading/Heading.tsx +7 -4
  274. package/src/markdoc/components/Tabs/TabList.tsx +6 -2
  275. package/src/markdoc/components/Tabs/Tabs.tsx +4 -2
  276. package/src/markdoc/components/Tabs/variables.ts +4 -4
  277. package/src/markdoc/tags/accordion-group.ts +3 -0
  278. package/src/markdoc/tags/admonition.ts +1 -1
  279. package/src/markdoc/tags/card.ts +14 -0
  280. package/src/markdoc/tags/connect-mcp.ts +3 -1
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef } from 'react';
1
+ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -6,14 +6,17 @@ import type { MCPOption } from '@redocly/theme/core/types';
6
6
 
7
7
  import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
8
8
  import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
9
+ import { DropdownMenuItemContent } from '@redocly/theme/components/Dropdown/DropdownMenuItemContent';
9
10
  import { Button } from '@redocly/theme/components/Button/Button';
11
+ import { SplitButton } from '@redocly/theme/components/SplitButton/SplitButton';
10
12
  import { CursorIcon } from '@redocly/theme/icons/CursorIcon/CursorIcon';
11
13
  import { VSCodeIcon } from '@redocly/theme/icons/VSCodeIcon/VSCodeIcon';
14
+ import { ClaudeIcon } from '@redocly/theme/icons/ClaudeIcon/ClaudeIcon';
15
+ import { CodexIcon } from '@redocly/theme/icons/CodexIcon/CodexIcon';
12
16
  import { CopyIcon } from '@redocly/theme/icons/CopyIcon/CopyIcon';
13
17
  import { CheckmarkFilledIcon } from '@redocly/theme/icons/CheckmarkFilledIcon/CheckmarkFilledIcon';
14
- import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
15
- import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';
16
- import { useConnectMCPButton, useOutsideClick, useThemeHooks } from '@redocly/theme/core/hooks';
18
+ import { useConnectMCPButton, useThemeHooks } from '@redocly/theme/core/hooks';
19
+ import { DEFAULT_MCP_OPTIONS } from '@redocly/theme/core/constants';
17
20
 
18
21
  type MenuOption = {
19
22
  key: MCPOption;
@@ -41,6 +44,22 @@ const MENU_OPTIONS: MenuOption[] = [
41
44
  descriptionTranslationKey: 'page.actions.connectMcp.vscodeDescription',
42
45
  descriptionDefault: 'Install MCP server on VS Code',
43
46
  },
47
+ {
48
+ key: 'claude-code',
49
+ icon: ClaudeIcon,
50
+ titleTranslationKey: 'page.actions.connectMcp.claudeCode',
51
+ titleDefault: 'Connect to Claude Code',
52
+ descriptionTranslationKey: 'page.actions.connectMcp.claudeCodeDescription',
53
+ descriptionDefault: 'Copy the Claude Code CLI command',
54
+ },
55
+ {
56
+ key: 'codex',
57
+ icon: CodexIcon,
58
+ titleTranslationKey: 'page.actions.connectMcp.codex',
59
+ titleDefault: 'Connect to Codex CLI',
60
+ descriptionTranslationKey: 'page.actions.connectMcp.codexDescription',
61
+ descriptionDefault: 'Copy the Codex CLI command',
62
+ },
44
63
  {
45
64
  key: 'copy',
46
65
  icon: CopyIcon,
@@ -51,6 +70,9 @@ const MENU_OPTIONS: MenuOption[] = [
51
70
  },
52
71
  ];
53
72
 
73
+ // Copy actions keep the dropdown open so the copied checkmark is visible.
74
+ const COPY_OPTIONS: MCPOption[] = ['claude-code', 'codex', 'copy'];
75
+
54
76
  export type ConnectMCPButtonProps = {
55
77
  placement?: 'top' | 'bottom';
56
78
  alignment?: 'start' | 'end';
@@ -60,16 +82,12 @@ export type ConnectMCPButtonProps = {
60
82
  export function ConnectMCPButton({
61
83
  placement = 'bottom',
62
84
  alignment = 'end',
63
- options = ['cursor', 'vscode', 'copy'],
85
+ options = DEFAULT_MCP_OPTIONS,
64
86
  }: ConnectMCPButtonProps): JSX.Element | null {
65
87
  const { useTranslate } = useThemeHooks();
66
88
  const { translate } = useTranslate();
67
- const [isOpen, setIsOpen] = useState(false);
68
- const wrapperRef = useRef<HTMLDivElement>(null);
69
-
70
- const { isCopied, visibleOptions, handleAction } = useConnectMCPButton({ options });
71
89
 
72
- useOutsideClick(wrapperRef, () => setIsOpen(false));
90
+ const { copiedOption, visibleOptions, handleAction } = useConnectMCPButton({ options });
73
91
 
74
92
  const menuOptions = MENU_OPTIONS.filter((option) => visibleOptions.includes(option.key));
75
93
  const primaryOption = menuOptions[0];
@@ -78,76 +96,70 @@ export function ConnectMCPButton({
78
96
  if (!primaryOption) return null;
79
97
 
80
98
  const PrimaryIcon = primaryOption.icon;
81
- // When copy is the primary action there is no dropdown item to show the copied state, so the
82
- // primary button itself must reflect it.
83
- const showPrimaryCopied = primaryOption.key === 'copy' && isCopied;
84
-
85
- const handleMenuAction = (key: MCPOption) => {
86
- handleAction(key);
87
- setIsOpen(false);
88
- };
99
+ const showPrimaryCopied = copiedOption === primaryOption.key;
89
100
 
90
101
  return (
91
- <ConnectMCPButtonWrapper ref={wrapperRef} data-component-name="Buttons/ConnectMCPButton">
92
- <SplitButtonGroup>
93
- <PrimaryButton
94
- variant="secondary"
95
- icon={
96
- showPrimaryCopied ? (
97
- <CheckmarkFilledIcon color="var(--color-success-base)" />
98
- ) : (
99
- <PrimaryIcon />
100
- )
101
- }
102
- onClick={() => handleAction(primaryOption.key)}
103
- >
104
- {translate(primaryOption.titleTranslationKey, primaryOption.titleDefault)}
105
- </PrimaryButton>
106
- {dropdownOptions.length > 0 && (
107
- <ChevronButton
102
+ <ConnectMCPButtonWrapper data-component-name="Buttons/ConnectMCPButton">
103
+ <SplitButton
104
+ variant="secondary"
105
+ placement={placement}
106
+ alignment={alignment}
107
+ closeOnSelect={false}
108
+ toggleAriaLabel={translate('page.actions.connectMcp.moreOptions', 'More options')}
109
+ button={
110
+ <Button
108
111
  variant="secondary"
109
- aria-label={translate('page.actions.connectMcp.moreOptions', 'More options')}
110
- aria-expanded={isOpen}
111
- aria-haspopup="menu"
112
- onClick={() => setIsOpen((prev) => !prev)}
112
+ icon={
113
+ showPrimaryCopied ? (
114
+ <CheckmarkFilledIcon color="var(--color-success-base)" />
115
+ ) : (
116
+ <PrimaryIcon />
117
+ )
118
+ }
119
+ onClick={() => handleAction(primaryOption.key)}
113
120
  >
114
- {isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
115
- </ChevronButton>
116
- )}
117
- </SplitButtonGroup>
118
-
119
- {isOpen && dropdownOptions.length > 0 && (
120
- <DropdownWrapper $placement={placement} $alignment={alignment}>
121
- <DropdownMenu>
122
- {dropdownOptions.map((option) => {
123
- const Icon = option.icon;
124
- const showCheckmark = option.key === 'copy' && isCopied;
125
-
126
- return (
127
- <DropdownMenuItem key={option.key} onAction={() => handleMenuAction(option.key)}>
128
- <MenuItemContent>
129
- <MenuItemIcon>
130
- {showCheckmark ? (
131
- <CheckmarkFilledIcon color="var(--color-success-base)" />
132
- ) : (
133
- <Icon />
134
- )}
135
- </MenuItemIcon>
136
- <MenuItemText>
137
- <MenuItemTitle>
138
- {translate(option.titleTranslationKey, option.titleDefault)}
139
- </MenuItemTitle>
140
- <MenuItemDescription>
141
- {translate(option.descriptionTranslationKey, option.descriptionDefault)}
142
- </MenuItemDescription>
143
- </MenuItemText>
144
- </MenuItemContent>
145
- </DropdownMenuItem>
146
- );
147
- })}
148
- </DropdownMenu>
149
- </DropdownWrapper>
150
- )}
121
+ {translate(primaryOption.titleTranslationKey, primaryOption.titleDefault)}
122
+ </Button>
123
+ }
124
+ >
125
+ {dropdownOptions.length > 0
126
+ ? ({ close }: { close: () => void }) => (
127
+ <DropdownMenu>
128
+ {dropdownOptions.map((option) => {
129
+ const Icon = option.icon;
130
+ const showCheckmark = copiedOption === option.key;
131
+
132
+ return (
133
+ <DropdownMenuItem
134
+ key={option.key}
135
+ onAction={() => {
136
+ handleAction(option.key);
137
+ if (!COPY_OPTIONS.includes(option.key)) {
138
+ close();
139
+ }
140
+ }}
141
+ >
142
+ <DropdownMenuItemContent
143
+ icon={
144
+ showCheckmark ? (
145
+ <CheckmarkFilledIcon color="var(--color-success-base)" />
146
+ ) : (
147
+ <Icon />
148
+ )
149
+ }
150
+ title={translate(option.titleTranslationKey, option.titleDefault)}
151
+ description={translate(
152
+ option.descriptionTranslationKey,
153
+ option.descriptionDefault,
154
+ )}
155
+ />
156
+ </DropdownMenuItem>
157
+ );
158
+ })}
159
+ </DropdownMenu>
160
+ )
161
+ : null}
162
+ </SplitButton>
151
163
  </ConnectMCPButtonWrapper>
152
164
  );
153
165
  }
@@ -156,72 +168,3 @@ const ConnectMCPButtonWrapper = styled.div`
156
168
  display: inline-block;
157
169
  position: relative;
158
170
  `;
159
-
160
- const SplitButtonGroup = styled.div`
161
- display: inline-flex;
162
- align-items: stretch;
163
- `;
164
-
165
- const PrimaryButton = styled(Button)`
166
- --button-gap: var(--connect-mcp-button-gap);
167
- border-top-right-radius: 0;
168
- border-bottom-right-radius: 0;
169
- `;
170
-
171
- const ChevronButton = styled(Button)`
172
- border-top-left-radius: 0;
173
- border-bottom-left-radius: 0;
174
- border-left: none;
175
- padding-inline: var(--connect-mcp-button-chevron-padding-inline);
176
- `;
177
-
178
- const DropdownWrapper = styled.div<{ $placement?: string; $alignment?: string }>`
179
- position: absolute;
180
- top: ${({ $placement }) => ($placement === 'top' ? 'auto' : '100%')};
181
- bottom: ${({ $placement }) => ($placement === 'top' ? '100%' : 'auto')};
182
- left: ${({ $alignment }) => ($alignment === 'start' ? '0' : 'auto')};
183
- right: ${({ $alignment }) => ($alignment === 'end' ? '0' : 'auto')};
184
- padding-top: var(--dropdown-menu-padding-top);
185
- z-index: var(--dropdown-menu-z-index);
186
- `;
187
-
188
- const MenuItemContent = styled.div`
189
- display: flex;
190
- align-items: center;
191
- gap: var(--connect-mcp-button-menu-item-gap);
192
- padding: var(--connect-mcp-button-menu-item-padding-block)
193
- var(--connect-mcp-button-menu-item-padding-inline);
194
- `;
195
-
196
- const MenuItemIcon = styled.div`
197
- display: flex;
198
- align-items: center;
199
- justify-content: center;
200
- width: var(--connect-mcp-button-menu-item-icon-size);
201
- height: var(--connect-mcp-button-menu-item-icon-size);
202
- flex-shrink: 0;
203
- border: var(--connect-mcp-button-menu-item-icon-border);
204
- border-radius: var(--connect-mcp-button-menu-item-icon-border-radius);
205
- color: var(--connect-mcp-button-menu-item-icon-color);
206
- `;
207
-
208
- const MenuItemText = styled.div`
209
- display: flex;
210
- flex-direction: column;
211
- gap: var(--connect-mcp-button-menu-item-text-gap);
212
- flex: 1;
213
- `;
214
-
215
- const MenuItemTitle = styled.div`
216
- font-size: var(--connect-mcp-button-menu-item-title-font-size);
217
- font-weight: var(--connect-mcp-button-menu-item-title-font-weight);
218
- line-height: var(--connect-mcp-button-menu-item-title-line-height);
219
- color: var(--connect-mcp-button-menu-item-title-color);
220
- `;
221
-
222
- const MenuItemDescription = styled.div`
223
- font-size: var(--connect-mcp-button-menu-item-description-font-size);
224
- font-weight: var(--connect-mcp-button-menu-item-description-font-weight);
225
- line-height: var(--connect-mcp-button-menu-item-description-line-height);
226
- color: var(--connect-mcp-button-menu-item-description-color);
227
- `;
@@ -1,4 +1,4 @@
1
- import React, { memo } from 'react';
1
+ import React, { memo, useEffect, useRef } from 'react';
2
2
 
3
3
  import type { JSX } from 'react';
4
4
  import type { TooltipProps } from '@redocly/theme/core/types';
@@ -52,11 +52,23 @@ function CopyButtonComponent({
52
52
  const { useTranslate } = useThemeHooks();
53
53
  const tooltip = useControl();
54
54
  const { translate } = useTranslate();
55
+ const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
56
+
57
+ useEffect(() => {
58
+ return () => {
59
+ if (closeTimerRef.current) {
60
+ clearTimeout(closeTimerRef.current);
61
+ }
62
+ };
63
+ }, []);
55
64
 
56
65
  const showTooltip = (duration: number = 1500): void => {
57
66
  tooltip.handleOpen();
58
67
 
59
- setTimeout(() => {
68
+ if (closeTimerRef.current) {
69
+ clearTimeout(closeTimerRef.current);
70
+ }
71
+ closeTimerRef.current = setTimeout(() => {
60
72
  tooltip.handleClose();
61
73
  }, duration);
62
74
  };
@@ -46,49 +46,7 @@ export const aiAssistantButton = css`
46
46
 
47
47
  /* Transition */
48
48
  --ai-assistant-button-transition: box-shadow 0.3s ease, transform 0.2s ease;
49
- `;
50
- /* eslint-enable theme/no-raw-colors-in-styles */
51
-
52
- export const connectMCPButton = css`
53
- /**
54
- * @tokens Connect MCP Button
55
- * @presenter Color
56
- */
57
-
58
- /* Button gap */
59
- --connect-mcp-button-gap: var(--spacing-xs);
60
49
 
61
- /* Chevron button */
62
- --connect-mcp-button-chevron-padding-inline: var(--spacing-xs);
63
-
64
- /* Menu item layout */
65
- --connect-mcp-button-menu-item-gap: var(--spacing-sm);
66
- --connect-mcp-button-menu-item-padding-block: var(--spacing-xxs);
67
- --connect-mcp-button-menu-item-padding-inline: 0;
68
-
69
- /* Menu item icon */
70
- --connect-mcp-button-menu-item-icon-size: 32px;
71
- --connect-mcp-button-menu-item-icon-border-color: var(--border-color-secondary);
72
- --connect-mcp-button-menu-item-icon-border-style: solid;
73
- --connect-mcp-button-menu-item-icon-border-width: 1px;
74
- --connect-mcp-button-menu-item-icon-border: var(--connect-mcp-button-menu-item-icon-border-width)
75
- var(--connect-mcp-button-menu-item-icon-border-style)
76
- var(--connect-mcp-button-menu-item-icon-border-color);
77
- --connect-mcp-button-menu-item-icon-border-radius: var(--border-radius);
78
- --connect-mcp-button-menu-item-icon-color: var(--icon-color-secondary);
79
-
80
- /* Menu item text */
81
- --connect-mcp-button-menu-item-text-gap: var(--spacing-xxs);
82
-
83
- /* Menu item title */
84
- --connect-mcp-button-menu-item-title-font-size: var(--font-size-base);
85
- --connect-mcp-button-menu-item-title-font-weight: var(--font-weight-regular);
86
- --connect-mcp-button-menu-item-title-line-height: var(--line-height-base);
87
- --connect-mcp-button-menu-item-title-color: var(--text-color-secondary);
88
-
89
- /* Menu item description */
90
- --connect-mcp-button-menu-item-description-font-size: var(--font-size-sm);
91
- --connect-mcp-button-menu-item-description-font-weight: var(--font-weight-regular);
92
- --connect-mcp-button-menu-item-description-line-height: var(--line-height-sm);
93
- --connect-mcp-button-menu-item-description-color: var(--text-color-description);
50
+ // @tokens End
94
51
  `;
52
+ /* eslint-enable theme/no-raw-colors-in-styles */
@@ -88,6 +88,7 @@ export function CatalogEntities(props: CatalogEntitiesProps): JSX.Element {
88
88
  (query.isPlaceholderData && entities && entities.length >= LOAD_MORE_THRESHOLD);
89
89
 
90
90
  useEffect(() => {
91
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the entities query lives in this ejectable component; the fetched total is mirrored up so the portal page header can show the count
91
92
  setEntitiesCounter(total || 0);
92
93
  }, [total, setEntitiesCounter]);
93
94
 
@@ -45,7 +45,7 @@ export function CatalogEntityLinks({ entity }: CatalogEntityLinksProps) {
45
45
  const LinksWrapper = styled.div`
46
46
  display: flex;
47
47
  flex-direction: column;
48
- border-radius: var(--border-radius);
48
+ border-radius: var(--border-radius-xl);
49
49
  background-color: var(--catalog-metadata-bg-color);
50
50
  transition: all 0.2s ease-in-out;
51
51
  margin: var(--spacing-lg) 0;
@@ -82,7 +82,7 @@ const JsonViewerWrapper = styled(JsonViewer)`
82
82
  const MetadataWrapper = styled.div`
83
83
  display: flex;
84
84
  flex-direction: column;
85
- border-radius: var(--border-radius);
85
+ border-radius: var(--border-radius-xl);
86
86
  background-color: var(--catalog-metadata-bg-color);
87
87
  transition: all 0.2s ease-in-out;
88
88
  margin-bottom: var(--spacing-base);
@@ -49,7 +49,7 @@ const PathLabel = styled.label`
49
49
  const LinksWrapper = styled.div`
50
50
  display: flex;
51
51
  flex-direction: column;
52
- border-radius: var(--border-radius);
52
+ border-radius: var(--border-radius-xl);
53
53
  background-color: var(--catalog-metadata-bg-color);
54
54
  transition: all 0.2s ease-in-out;
55
55
  margin: 0 0 var(--spacing-base) 0;
@@ -1,4 +1,4 @@
1
- import React, { JSX, useState, useEffect, useMemo } from 'react';
1
+ import React, { JSX, useState, useMemo } from 'react';
2
2
 
3
3
  import type { CatalogEntityConfig, EntitiesCatalogConfig } from '@redocly/config';
4
4
  import type { BffCatalogEntity, BffCatalogRelatedEntityList } from '@redocly/theme/core/types';
@@ -35,10 +35,12 @@ export function CatalogEntityRelations({
35
35
  const { sortOption, setSortOption, handleSortClick, isColumnSorted } = useCatalogSort();
36
36
 
37
37
  const [filter, setFilter] = useState<string | undefined>(ENTITY_DEFAULT_FILTERS[entity.type]);
38
+ const [prevEntityKey, setPrevEntityKey] = useState(entity.key);
38
39
 
39
- useEffect(() => {
40
- setFilter(ENTITY_DEFAULT_FILTERS[entity.key]);
41
- }, [entity.key]);
40
+ if (prevEntityKey !== entity.key) {
41
+ setPrevEntityKey(entity.key);
42
+ setFilter(ENTITY_DEFAULT_FILTERS[entity.type]);
43
+ }
42
44
 
43
45
  const combinedFilter = useMemo(() => {
44
46
  const baseFilter = filter ?? ENTITY_DEFAULT_FILTERS[entity.type];
@@ -85,7 +85,7 @@ export function CatalogEntitySchema({
85
85
  const MetadataWrapper = styled.div`
86
86
  display: flex;
87
87
  flex-direction: column;
88
- border-radius: var(--border-radius);
88
+ border-radius: var(--border-radius-xl);
89
89
  background-color: var(--catalog-metadata-bg-color);
90
90
  margin-bottom: var(--spacing-lg);
91
91
  `;
@@ -127,6 +127,7 @@ const StyledSelect = styled(Select)<SelectProps>`
127
127
  --select-text-color: var(--filter-select-color);
128
128
  border: 1px solid var(--filter-select-border-color);
129
129
  --select-border-radius: var(--filter-select-border-radius);
130
+ --select-input-border-radius: var(--filter-select-border-radius);
130
131
  padding: var(--filter-select-padding);
131
132
 
132
133
  &:hover {
@@ -1,31 +1,16 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import { TableIcon } from '@redocly/theme/icons/TableIcon/TableIcon';
5
5
  import { CardsIcon } from '@redocly/theme/icons/CardsIcon/CardsIcon';
6
6
  import { CatalogViewMode } from '@redocly/theme/core/types';
7
7
 
8
- function useViewMode(initialViewMode: CatalogViewMode) {
9
- const [viewMode, setViewMode] = useState<CatalogViewMode | null>(null);
10
-
11
- useEffect(() => {
12
- setViewMode(initialViewMode);
13
- }, [initialViewMode]);
14
-
15
- return viewMode;
16
- }
17
-
18
8
  export type CatalogViewModeToggleProps = {
19
9
  viewMode: CatalogViewMode;
20
10
  onViewModeChange: (mode: CatalogViewMode) => void;
21
11
  };
22
12
 
23
- export function CatalogViewModeToggle({
24
- viewMode: initialViewMode,
25
- onViewModeChange,
26
- }: CatalogViewModeToggleProps) {
27
- const viewMode = useViewMode(initialViewMode);
28
-
13
+ export function CatalogViewModeToggle({ viewMode, onViewModeChange }: CatalogViewModeToggleProps) {
29
14
  return (
30
15
  <ViewModeToggleWrapper data-component-name="Catalog/CatalogViewModeToggle">
31
16
  <ViewModeButton
@@ -190,7 +190,7 @@ export const catalog = css`
190
190
  */
191
191
  --catalog-sort-trigger-gap: var(--spacing-xs);
192
192
  --catalog-sort-trigger-padding: 6px 8px;
193
- --catalog-sort-trigger-border-radius: var(--border-radius-md);
193
+ --catalog-sort-trigger-border-radius: var(--border-radius-lg);
194
194
  --catalog-sort-trigger-hover-bg-color: var(--button-bg-color-secondary-hover);
195
195
  --catalog-sort-icon-color: var(--text-color-secondary);
196
196
  --catalog-sort-text-font-size: var(--font-size-sm);
@@ -220,7 +220,7 @@ export const catalog = css`
220
220
  --catalog-card-border-width: var(--border-width);
221
221
  --catalog-card-border-style: var(--border-style);
222
222
  --catalog-card-border-color-hover: var(--border-color-primary);
223
- --catalog-card-border-radius: var(--border-radius-lg);
223
+ --catalog-card-border-radius: var(--border-radius-xl);
224
224
  --catalog-cards-group-margin: 0 0 var(--spacing-base) 0;
225
225
  --catalog-cards-group-gap: var(--spacing-lg);
226
226
 
@@ -229,7 +229,7 @@ export const catalog = css`
229
229
  */
230
230
  --catalog-card-icon-width: 30px;
231
231
  --catalog-card-icon-height: 30px;
232
- --catalog-card-icon-border-radius: 4px;
232
+ --catalog-card-icon-border-radius: var(--border-radius);
233
233
  --catalog-card-icon-bg-color: var(--bg-color);
234
234
  --catalog-card-icon-border-color: var(--border-color-secondary);
235
235
  --catalog-card-icon-border-width: 1px;
@@ -307,7 +307,7 @@ export const catalog = css`
307
307
  --catalog-actions-min-height: 40px;
308
308
  --catalog-actions-bg-color: var(--bg-color-tonal);
309
309
 
310
- --catalog-page-badge-border-radius: var(--border-radius);
310
+ --catalog-page-badge-border-radius: var(--badge-border-radius);
311
311
  --catalog-page-badge-padding: 0 var(--spacing-xs);
312
312
  --catalog-page-badge-font-size: var(--font-size-base);
313
313
  --catalog-page-badge-line-height: var(--line-height-base);
@@ -343,7 +343,7 @@ export const catalog = css`
343
343
  */
344
344
  --catalog-table-icon-width: 32px;
345
345
  --catalog-table-icon-height: 32px;
346
- --catalog-table-icon-border-radius: 4px;
346
+ --catalog-table-icon-border-radius: var(--border-radius);
347
347
  --catalog-table-icon-bg-color: var(--bg-color-tonal);
348
348
  --catalog-table-icon-border-color: var(--border-color-secondary);
349
349
  --catalog-table-icon-border-width: 1px;
@@ -57,7 +57,7 @@ export const catalogClassic = css`
57
57
  --catalog-classic-card-border-width: var(--border-width);
58
58
  --catalog-classic-card-border-style: var(--border-style);
59
59
  --catalog-classic-card-border-color-hover: var(--border-color-primary);
60
- --catalog-classic-card-border-radius: var(--border-radius-xxl);
60
+ --catalog-classic-card-border-radius: var(--border-radius-xl);
61
61
  --catalog-classic-cards-group-margin: 0 0 var(--spacing-base) 0;
62
62
  --catalog-classic-cards-group-gap: var(--spacing-xl);
63
63
 
@@ -94,7 +94,7 @@ export const catalogClassic = css`
94
94
  --catalog-classic-actions-min-height: 40px;
95
95
  --catalog-classic-actions-bg-color: var(--bg-color-tonal);
96
96
 
97
- --catalog-classic-page-badge-border-radius: var(--border-radius);
97
+ --catalog-classic-page-badge-border-radius: var(--border-radius-md);
98
98
  --catalog-classic-page-badge-padding: 0 var(--spacing-xs);
99
99
  --catalog-classic-page-badge-font-size: var(--font-size-base);
100
100
  --catalog-classic-page-badge-line-height: var(--line-height-base);
@@ -130,6 +130,7 @@ export function CodeBlock({
130
130
  useEffect(() => {
131
131
  const _source = source || externalSource?.sample?.get?.(externalSource);
132
132
  if (_source) {
133
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-derived-state
133
134
  setSourceCode(_source);
134
135
  }
135
136
  }, [source, externalSource]);
@@ -204,7 +205,7 @@ const ContainerWrapper = styled.div`
204
205
 
205
206
  const CodeBlockWrapper = styled.div`
206
207
  border: 1px solid var(--border-color-secondary);
207
- border-radius: var(--border-radius);
208
+ border-radius: var(--border-radius-xl);
208
209
  background-color: var(--code-block-bg-color);
209
210
  margin: 0 0 var(--spacing-sm);
210
211
 
@@ -55,6 +55,7 @@ const CodeBlockContainerComponent = styled.pre<{
55
55
  max-height: ${({ $maxHeight }) =>
56
56
  $maxHeight ? $maxHeight : 'var(--code-block-max-height, 600px);'};
57
57
  word-break: var(--code-block-word-break, initial);
58
+ border-radius: var(--border-radius-xl);
58
59
 
59
60
  pre,
60
61
  code {
@@ -10,7 +10,8 @@ import React, {
10
10
  import { createPortal } from 'react-dom';
11
11
  import styled, { css } from 'styled-components';
12
12
 
13
- import type { CSSProperties, PropsWithChildren, ReactElement } from 'react';
13
+ import type { CSSProperties, ReactElement } from 'react';
14
+ import type { RenderableChildren } from '@redocly/theme/core/types';
14
15
 
15
16
  import { useOutsideClick, useControlledState } from '@redocly/theme/core/hooks';
16
17
  import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
@@ -26,8 +27,14 @@ interface TriggerProps {
26
27
  [key: string]: unknown;
27
28
  }
28
29
 
29
- export type DropdownProps = PropsWithChildren<{
30
+ type DropdownMenuContext = {
31
+ close: () => void;
32
+ isOpen: boolean;
33
+ };
34
+
35
+ export type DropdownProps = {
30
36
  trigger: React.ReactNode;
37
+ children: RenderableChildren<DropdownMenuContext>;
31
38
  triggerEvent?: 'click' | 'hover';
32
39
  placement?: 'top' | 'bottom';
33
40
  alignment?: 'start' | 'end';
@@ -43,7 +50,7 @@ export type DropdownProps = PropsWithChildren<{
43
50
  onClick?: (event: React.UIEvent) => void;
44
51
  onClose?: () => void;
45
52
  onOpen?: () => void;
46
- }>;
53
+ };
47
54
 
48
55
  export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
49
56
  (
@@ -78,6 +85,9 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
78
85
  };
79
86
 
80
87
  const handleClose = () => {
88
+ if (!isOpen) {
89
+ return;
90
+ }
81
91
  setIsOpen(false);
82
92
  onClose?.();
83
93
  };
@@ -137,10 +147,14 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
137
147
 
138
148
  useOutsideClick(portalled ? [wrapperRef, menuRef] : wrapperRef, handleClose);
139
149
 
150
+ const menuContent =
151
+ typeof children === 'function' ? children({ close: handleClose, isOpen }) : children;
152
+
140
153
  const triggerChild = React.Children.only(trigger) as ReactElement<TriggerProps>;
141
154
 
142
155
  const dropdownTrigger = cloneElement(triggerChild, {
143
156
  onClick: triggerEvent === 'click' ? handleToggle : undefined,
157
+ 'aria-expanded': isOpen,
144
158
  icon: withArrow ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined,
145
159
  ...(withArrow ? { iconPosition: 'right' } : {}),
146
160
  ...triggerChild.props,
@@ -157,7 +171,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
157
171
  style={portalled ? menuStyle : undefined}
158
172
  onClick={closeOnClick ? handleChildClick : undefined}
159
173
  >
160
- {children}
174
+ {menuContent}
161
175
  </ChildrenWrapper>
162
176
  );
163
177
 
@@ -198,6 +212,13 @@ const DropdownWrapper = styled.div`
198
212
  position: relative;
199
213
 
200
214
  text-decoration: none;
215
+
216
+ /* The trigger keeps its hovered appearance while the menu is open. */
217
+ > .button[aria-expanded='true'] {
218
+ background-color: var(--button-bg-color-hover);
219
+ color: var(--button-color-hover);
220
+ border-color: var(--button-border-color-hover);
221
+ }
201
222
  `;
202
223
 
203
224
  const ChildrenWrapper = styled.div<{