@redocly/theme 0.69.0-next.8 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/lib/components/Accordion/Accordion.js +5 -1
  2. package/lib/components/Catalog/CatalogEntities.js +1 -0
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +5 -3
  4. package/lib/components/Catalog/CatalogViewModeToggle.d.ts +1 -1
  5. package/lib/components/Catalog/CatalogViewModeToggle.js +2 -43
  6. package/lib/components/Filter/FilterInput.js +4 -2
  7. package/lib/components/JsonViewer/Helpers.js +12 -42
  8. package/lib/components/MCP/McpConnectionTabs.js +21 -11
  9. package/lib/components/Markdown/Markdown.js +1 -3
  10. package/lib/components/Markdown/styles/base-table.js +8 -0
  11. package/lib/components/Markdown/variables.js +1 -2
  12. package/lib/components/Panel/Panel.js +5 -1
  13. package/lib/components/Search/FilterFields/SearchFilterFieldSelect.js +22 -32
  14. package/lib/components/Search/SearchAiDialog.js +3 -0
  15. package/lib/components/Search/SearchItem.js +5 -3
  16. package/lib/components/Search/variables.js +1 -0
  17. package/lib/components/Select/Select.js +10 -5
  18. package/lib/components/Sidebar/Sidebar.js +2 -38
  19. package/lib/core/contexts/AccordionGroupContext.d.ts +1 -0
  20. package/lib/core/contexts/AccordionGroupContext.js +6 -0
  21. package/lib/core/contexts/PageActionsContext.d.ts +5 -0
  22. package/lib/core/contexts/PageActionsContext.js +10 -0
  23. package/lib/core/contexts/index.d.ts +2 -0
  24. package/lib/core/contexts/index.js +2 -0
  25. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +5 -17
  26. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +2 -0
  27. package/lib/core/hooks/menu/use-menu-item-expanded.js +1 -0
  28. package/lib/core/hooks/use-active-heading.js +4 -0
  29. package/lib/core/hooks/use-connect-mcp-button.js +7 -2
  30. package/lib/core/hooks/use-local-state.js +1 -0
  31. package/lib/core/hooks/use-mobile-menu.js +10 -2
  32. package/lib/core/hooks/use-product-picker.js +1 -1
  33. package/lib/core/hooks/use-tabs.js +4 -2
  34. package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
  35. package/lib/core/hooks/use-telemetry-fallback.js +2 -0
  36. package/lib/core/styles/global.js +1 -0
  37. package/lib/core/utils/class-names.d.ts +1 -0
  38. package/lib/core/utils/class-names.js +7 -0
  39. package/lib/markdoc/components/Accordion/Accordion.js +6 -6
  40. package/lib/markdoc/components/Accordion/AccordionGroup.d.ts +2 -1
  41. package/lib/markdoc/components/Accordion/AccordionGroup.js +39 -3
  42. package/lib/markdoc/components/Heading/Heading.js +5 -2
  43. package/lib/markdoc/tags/accordion-group.js +3 -0
  44. package/package.json +2 -2
  45. package/src/components/Accordion/Accordion.tsx +7 -3
  46. package/src/components/Catalog/CatalogEntities.tsx +1 -0
  47. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +6 -4
  48. package/src/components/Catalog/CatalogViewModeToggle.tsx +2 -17
  49. package/src/components/Filter/FilterInput.tsx +5 -3
  50. package/src/components/JsonViewer/Helpers.tsx +13 -9
  51. package/src/components/MCP/McpConnectionTabs.tsx +32 -10
  52. package/src/components/Markdown/Markdown.tsx +1 -3
  53. package/src/components/Markdown/styles/base-table.ts +8 -0
  54. package/src/components/Markdown/variables.ts +1 -2
  55. package/src/components/Panel/Panel.tsx +7 -3
  56. package/src/components/Search/FilterFields/SearchFilterFieldSelect.tsx +32 -40
  57. package/src/components/Search/SearchAiDialog.tsx +3 -0
  58. package/src/components/Search/SearchItem.tsx +6 -1
  59. package/src/components/Search/variables.ts +1 -0
  60. package/src/components/Select/Select.tsx +12 -6
  61. package/src/components/Sidebar/Sidebar.tsx +2 -6
  62. package/src/core/contexts/AccordionGroupContext.tsx +3 -0
  63. package/src/core/contexts/PageActionsContext.tsx +7 -0
  64. package/src/core/contexts/index.ts +2 -0
  65. package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
  66. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +6 -20
  67. package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +2 -0
  68. package/src/core/hooks/menu/use-menu-item-expanded.ts +1 -0
  69. package/src/core/hooks/use-active-heading.ts +4 -0
  70. package/src/core/hooks/use-connect-mcp-button.ts +8 -2
  71. package/src/core/hooks/use-local-state.ts +1 -0
  72. package/src/core/hooks/use-mobile-menu.ts +11 -3
  73. package/src/core/hooks/use-product-picker.ts +2 -2
  74. package/src/core/hooks/use-tabs.ts +4 -2
  75. package/src/core/hooks/use-telemetry-fallback.ts +2 -0
  76. package/src/core/styles/global.ts +1 -0
  77. package/src/core/utils/class-names.ts +8 -0
  78. package/src/markdoc/components/Accordion/Accordion.tsx +8 -2
  79. package/src/markdoc/components/Accordion/AccordionGroup.tsx +9 -2
  80. package/src/markdoc/components/Heading/Heading.tsx +7 -4
  81. package/src/markdoc/tags/accordion-group.ts +3 -0
@@ -16,9 +16,6 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
16
16
  const location = (0, react_router_1.useLocation)();
17
17
  const navigate = (0, react_router_1.useNavigate)();
18
18
  const searchParams = (0, react_1.useMemo)(() => new URLSearchParams(location.search), [location.search]);
19
- const filtersRef = (0, react_1.useRef)(filters);
20
- const inputsRef = (0, react_1.useRef)(inputs);
21
- const togglesRef = (0, react_1.useRef)(toggles);
22
19
  const getInitialState = () => {
23
20
  var _a, _b;
24
21
  var _c, _d;
@@ -36,18 +33,10 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
36
33
  return state;
37
34
  };
38
35
  const [controlsState, setControlsState] = (0, react_1.useState)(getInitialState);
39
- (0, react_1.useEffect)(() => {
40
- const sameProps = [
41
- JSON.stringify(filters) === JSON.stringify(filtersRef.current),
42
- JSON.stringify(inputs) === JSON.stringify(inputsRef.current),
43
- JSON.stringify(toggles) === JSON.stringify(togglesRef.current),
44
- ];
45
- if (sameProps.every(Boolean)) {
46
- return;
47
- }
48
- filtersRef.current = filters;
49
- inputsRef.current = inputs;
50
- togglesRef.current = toggles;
36
+ const controlsKey = JSON.stringify([filters, inputs, toggles]);
37
+ const [prevControlsKey, setPrevControlsKey] = (0, react_1.useState)(controlsKey);
38
+ if (prevControlsKey !== controlsKey) {
39
+ setPrevControlsKey(controlsKey);
51
40
  const newState = getInitialState();
52
41
  // Preserve existing values where control type hasn't changed
53
42
  Object.entries(newState).forEach(([id, control]) => {
@@ -58,8 +47,7 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
58
47
  }
59
48
  });
60
49
  setControlsState(newState);
61
- // eslint-disable-next-line react-hooks/exhaustive-deps
62
- }, [filters, inputs, toggles, enableDeepLink]);
50
+ }
63
51
  const changeControlState = (id, value) => {
64
52
  setControlsState((prev) => {
65
53
  const control = prev[id];
@@ -58,6 +58,7 @@ function useCodeWalkthroughSteps({ steps, enableDeepLink, root, }) {
58
58
  startOffset: markersMinTopOffset < endOffset ? startOffset : 0,
59
59
  });
60
60
  }
61
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- marker geometry is derived from post-layout measurements (offsetTop, clientHeight, window.innerHeight)
61
62
  setMarkers(markers.reduce((acc, marker, index) => {
62
63
  var _a;
63
64
  const step = visibleSteps[index];
@@ -171,6 +172,7 @@ function useCodeWalkthroughSteps({ steps, enableDeepLink, root, }) {
171
172
  }
172
173
  const rootTopOffset = (_c = (_a = root.current) === null || _a === void 0 ? void 0 : _a.offsetTop) !== null && _c !== void 0 ? _c : 0;
173
174
  if (!activeStep && rootTopOffset <= options.navbarHeight) {
175
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-derived-state -- seeded from root.current.offsetTop, a post-layout measurement
174
176
  setActiveStep(((_b = visibleSteps[0]) === null || _b === void 0 ? void 0 : _b.id) || null);
175
177
  }
176
178
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -11,6 +11,7 @@ function useMenuItemExpanded(item) {
11
11
  // it's important for transitions
12
12
  const [controlledValue, setControlledValue] = (0, react_1.useState)(isItemExpanded(item));
13
13
  (0, react_1.useEffect)(() => {
14
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-derived-state -- deferred a commit on purpose so CSS transitions still run
14
15
  setIsExpanded(controlledValue);
15
16
  }, [controlledValue]);
16
17
  (0, react_1.useEffect)(() => {
@@ -205,19 +205,23 @@ function useActiveHeading(contentElement, displayedHeadings) {
205
205
  return;
206
206
  }
207
207
  const headers = findHeaders(contentElement);
208
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- headings and the initial active heading come from DOM queries and scroll measurement, available only after layout
208
209
  setHeadingElements(headers);
209
210
  if (headers.length > 0 && !heading) {
210
211
  if (isTopOfPage()) {
212
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
211
213
  setHeading(headers[0].id);
212
214
  }
213
215
  else {
214
216
  const totalHeight = scrollY + window.innerHeight;
215
217
  if (totalHeight >= document.body.scrollHeight - 10) {
218
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
216
219
  setHeading(((_a = headers[headers.length - 1]) === null || _a === void 0 ? void 0 : _a.id) || undefined);
217
220
  }
218
221
  else {
219
222
  const triggerOffset = getTriggerOffset();
220
223
  const activeHeader = findActiveHeaderByVirtualPosition(headers, scrollY, triggerOffset);
224
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-chain-state-updates -- see above
221
225
  setHeading(activeHeader.id);
222
226
  }
223
227
  }
@@ -11,15 +11,20 @@ const use_mcp_config_1 = require("./use-mcp-config");
11
11
  const clipboard_service_1 = require("../utils/clipboard-service");
12
12
  const mcp_1 = require("../constants/mcp");
13
13
  const mcp_2 = require("../utils/mcp");
14
+ const get_base_data_attributes_1 = require("../utils/telemetry/get-base-data-attributes");
14
15
  const COPIED_RESET_TIMEOUT = 1000;
15
16
  function useConnectMCPButton({ options = mcp_1.DEFAULT_MCP_OPTIONS, } = {}) {
16
- const { useTranslate } = (0, use_theme_hooks_1.useThemeHooks)();
17
+ const { useTranslate, useTelemetry } = (0, use_theme_hooks_1.useThemeHooks)();
17
18
  const { translate } = useTranslate();
19
+ const telemetry = useTelemetry();
18
20
  const { serverName, serverUrl, cursorUrl, vscodeUrl } = (0, use_mcp_config_1.useMCPConfig)();
19
21
  const [copiedOption, setCopiedOption] = (0, react_1.useState)(null);
20
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
21
23
  const resetCopied = (0, react_1.useCallback)((0, lodash_debounce_1.default)(() => setCopiedOption(null), COPIED_RESET_TIMEOUT), []);
22
24
  const handleAction = (0, react_1.useCallback)((action) => {
25
+ telemetry.sendConnectMcpClickedMessage([
26
+ Object.assign(Object.assign({}, (0, get_base_data_attributes_1.getBaseDataAttributes)('connectMcp', 'button')), { option: action }),
27
+ ]);
23
28
  const connection = { serverName, url: serverUrl };
24
29
  const copyTextMap = {
25
30
  copy: JSON.stringify({ [serverName]: { url: serverUrl, description: 'MCP Server' } }, null, 2),
@@ -38,7 +43,7 @@ function useConnectMCPButton({ options = mcp_1.DEFAULT_MCP_OPTIONS, } = {}) {
38
43
  vscode: vscodeUrl,
39
44
  };
40
45
  window.open(urlMap[action], '_blank');
41
- }, [cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied]);
46
+ }, [cursorUrl, vscodeUrl, serverUrl, serverName, resetCopied, telemetry]);
42
47
  const triggerButtonText = (0, react_1.useMemo)(() => translate('page.actions.connectMcp', 'Connect MCP'), [translate]);
43
48
  const visibleOptions = (0, react_1.useMemo)(() => options.filter(Boolean), [options]);
44
49
  return {
@@ -22,6 +22,7 @@ function useLocalState(key, initialValue) {
22
22
  (0, react_1.useEffect)(() => {
23
23
  if (!(0, js_utils_1.isBrowser)())
24
24
  return;
25
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-derived-state -- localStorage is read after mount so SSR and client hydrate from the same value
25
26
  setValue(getStoredValue(key, initialValue));
26
27
  // eslint-disable-next-line react-hooks/exhaustive-deps
27
28
  }, [key]);
@@ -7,9 +7,17 @@ const use_modal_scroll_lock_1 = require("./use-modal-scroll-lock");
7
7
  function useMobileMenu(initialState = false) {
8
8
  const location = (0, react_router_1.useLocation)();
9
9
  const [isOpen, setIsOpen] = (0, react_1.useState)(initialState);
10
- (0, react_1.useEffect)(() => setIsOpen(false), [location.pathname, location.hash]);
10
+ const locationKey = `${location.pathname}${location.hash}`;
11
+ const [prevLocationKey, setPrevLocationKey] = (0, react_1.useState)(locationKey);
12
+ if (isOpen && prevLocationKey !== locationKey) {
13
+ setPrevLocationKey(locationKey);
14
+ setIsOpen(false);
15
+ }
11
16
  (0, use_modal_scroll_lock_1.useModalScrollLock)(isOpen);
12
- const openMobileMenu = () => setIsOpen(true);
17
+ const openMobileMenu = () => {
18
+ setPrevLocationKey(locationKey);
19
+ setIsOpen(true);
20
+ };
13
21
  const closeMobileMenu = () => setIsOpen(false);
14
22
  return { isOpen, openMobileMenu, closeMobileMenu };
15
23
  }
@@ -33,7 +33,7 @@ function useProductPicker() {
33
33
  Array.from(root.classList)
34
34
  .filter((c) => c.startsWith('product-'))
35
35
  .forEach((c) => root.classList.remove(c));
36
- root.classList.add((0, utils_1.getProductClassName)(freshProduct.name));
36
+ root.classList.add((0, utils_2.getSafeClassName)((0, utils_1.getProductClassName)(freshProduct.name)));
37
37
  }
38
38
  loadAndNavigate({ navigate, to: (0, utils_2.withPathPrefix)(freshProduct.link) });
39
39
  }
@@ -220,12 +220,14 @@ const useActiveTab = ({ initialTab, tabsId }) => {
220
220
  const [activeTab, setActiveTab] = (0, react_1.useState)(undefined);
221
221
  const resolvedActiveTab = activeTab !== null && activeTab !== void 0 ? activeTab : initialTab;
222
222
  const prevActiveTabRef = (0, react_1.useRef)(resolvedActiveTab);
223
+ const isTabResolvedFromHashRef = (0, react_1.useRef)(false);
223
224
  (0, react_1.useEffect)(() => {
224
- if (activeTab !== undefined) {
225
+ if (isTabResolvedFromHashRef.current) {
225
226
  return;
226
227
  }
228
+ isTabResolvedFromHashRef.current = true;
227
229
  setActiveTab(getInitialTab({ initialTab, hash: location.hash, tabsId }));
228
- }, [activeTab, initialTab, location.hash, tabsId]);
230
+ }, [initialTab, location.hash, tabsId]);
229
231
  (0, react_1.useEffect)(() => {
230
232
  const hasActiveTabChanged = prevActiveTabRef.current !== resolvedActiveTab;
231
233
  if (!tabsId || !hasActiveTabChanged) {
@@ -34,6 +34,8 @@ export declare const useTelemetryFallback: () => {
34
34
  sendProductPickerChangedMessage: () => void;
35
35
  sendLogoClickedMessage: () => void;
36
36
  sendCodeSnippetReportedMessage: () => void;
37
+ sendConnectMcpClickedMessage: () => void;
38
+ sendMcpConnectionSnippetCopiedMessage: () => void;
37
39
  sendMobileMenuClosedMessage: () => void;
38
40
  sendMobileMenuOpenedMessage: () => void;
39
41
  sendPageViewedMessage: () => void;
@@ -39,6 +39,8 @@ const useTelemetryFallback = () => ({
39
39
  sendProductPickerChangedMessage: () => { },
40
40
  sendLogoClickedMessage: () => { },
41
41
  sendCodeSnippetReportedMessage: () => { },
42
+ sendConnectMcpClickedMessage: () => { },
43
+ sendMcpConnectionSnippetCopiedMessage: () => { },
42
44
  sendMobileMenuClosedMessage: () => { },
43
45
  sendMobileMenuOpenedMessage: () => { },
44
46
  sendPageViewedMessage: () => { },
@@ -592,6 +592,7 @@ const links = (0, styled_components_1.css) `
592
592
 
593
593
  // @tokens End
594
594
  `;
595
+ // oxlint-disable-next-line theme/css-variable-pattern -- `--schema-labels--text-color` is a documented public variable
595
596
  const apiReferenceDocs = (0, styled_components_1.css) `
596
597
  /* === ref docs and graphql docs specific === */
597
598
 
@@ -1 +1,2 @@
1
+ export declare function getSafeClassName(className: string): string;
1
2
  export declare function concatClassNames(...args: Array<string | undefined>): string;
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSafeClassName = getSafeClassName;
3
4
  exports.concatClassNames = concatClassNames;
4
5
  const SPACE_DELIMITER = ' ';
6
+ // EasyList/AdGuard ship generic cosmetic rules that hide these exact class tokens on every
7
+ // site; the trailing underscore keeps the class usable without matching the filter lists.
8
+ const ADBLOCK_HIDDEN_CLASS_NAMES = new Set(['product-ad', 'product-ads', 'product-inlist-ad']);
9
+ function getSafeClassName(className) {
10
+ return ADBLOCK_HIDDEN_CLASS_NAMES.has(className) ? `${className}_` : className;
11
+ }
5
12
  function concatClassNames(...args) {
6
13
  return args
7
14
  .map((className) => (className ? className.trim() : className))
@@ -32,21 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.Accordion = Accordion;
40
- const react_1 = __importDefault(require("react"));
37
+ const react_1 = __importStar(require("react"));
41
38
  const styled_components_1 = __importStar(require("styled-components"));
42
39
  const ChevronRightIcon_1 = require("../../../icons/ChevronRightIcon/ChevronRightIcon");
43
40
  const constants_1 = require("../../../core/constants");
41
+ const contexts_1 = require("../../../core/contexts");
44
42
  function Accordion({ title, expanded = false, className, children, 'data-source': dataSource, 'data-hash': dataHash, }) {
45
- return (react_1.default.createElement(AccordionWrapper, { className: className, open: expanded, "data-component-name": constants_1.ACCORDION_COMPONENT_NAME, "data-source": dataSource, "data-hash": dataHash },
43
+ const groupName = (0, react_1.useContext)(contexts_1.AccordionGroupContext);
44
+ return (react_1.default.createElement(AccordionWrapper, { className: className, open: expanded, name: groupName !== null && groupName !== void 0 ? groupName : undefined, "data-component-name": constants_1.ACCORDION_COMPONENT_NAME, "data-source": dataSource, "data-hash": dataHash },
46
45
  react_1.default.createElement(AccordionSummary, null,
47
46
  react_1.default.createElement(ChevronRightIcon_1.ChevronRightIcon, { color: "--md-accordion-chevron-icon-color", size: "var(--md-accordion-chevron-icon-size)" }),
48
47
  title),
49
- react_1.default.createElement(AccordionBody, null, children)));
48
+ react_1.default.createElement(AccordionBody, null,
49
+ react_1.default.createElement(contexts_1.AccordionGroupContext.Provider, { value: null }, children))));
50
50
  }
51
51
  const AccordionWrapper = styled_components_1.default.details `
52
52
  margin: var(--md-accordion-margin);
@@ -1,5 +1,6 @@
1
1
  import type { JSX, PropsWithChildren } from 'react';
2
2
  export type AccordionGroupProps = {
3
+ singleExpanded?: boolean;
3
4
  className?: string;
4
5
  };
5
- export declare function AccordionGroup({ className, children, }: PropsWithChildren<AccordionGroupProps>): JSX.Element;
6
+ export declare function AccordionGroup({ singleExpanded, className, children, }: PropsWithChildren<AccordionGroupProps>): JSX.Element;
@@ -1,14 +1,50 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.AccordionGroup = AccordionGroup;
7
- const react_1 = __importDefault(require("react"));
40
+ const react_1 = __importStar(require("react"));
8
41
  const styled_components_1 = __importDefault(require("styled-components"));
9
42
  const constants_1 = require("../../../core/constants");
10
- function AccordionGroup({ className, children, }) {
11
- return (react_1.default.createElement(AccordionGroupWrapper, { className: className, "data-component-name": constants_1.ACCORDION_GROUP_COMPONENT_NAME }, children));
43
+ const contexts_1 = require("../../../core/contexts");
44
+ function AccordionGroup({ singleExpanded = false, className, children, }) {
45
+ const groupName = (0, react_1.useId)();
46
+ return (react_1.default.createElement(AccordionGroupWrapper, { className: className, "data-component-name": constants_1.ACCORDION_GROUP_COMPONENT_NAME },
47
+ react_1.default.createElement(contexts_1.AccordionGroupContext.Provider, { value: singleExpanded ? groupName : null }, children)));
12
48
  }
13
49
  const AccordionGroupWrapper = styled_components_1.default.div `
14
50
  margin: var(--md-accordion-group-margin);
@@ -45,6 +45,7 @@ const utils_1 = require("../../../core/utils");
45
45
  const LinkIcon_1 = require("../../../icons/LinkIcon/LinkIcon");
46
46
  const PageActions_1 = require("../../../components/PageActions/PageActions");
47
47
  const hooks_1 = require("../../../core/hooks");
48
+ const contexts_1 = require("../../../core/contexts");
48
49
  const constants_1 = require("../../../core/constants");
49
50
  function renderWithSpanWrapper(children) {
50
51
  const childrenArray = react_1.default.Children.toArray(children);
@@ -68,10 +69,12 @@ function renderWithSpanWrapper(children) {
68
69
  }
69
70
  function Heading({ level, id, children, 'data-source': dataSource, 'data-hash': dataHash, className, __idx, }) {
70
71
  var _a, _b;
72
+ var _c;
71
73
  const { pathname } = (0, react_router_1.useLocation)();
72
74
  const { usePageProps } = (0, hooks_1.useThemeHooks)();
73
75
  const pageProps = usePageProps();
74
- const isMarkdownPage = ((_a = pageProps === null || pageProps === void 0 ? void 0 : pageProps.metadata) === null || _a === void 0 ? void 0 : _a.type) === 'markdown' || ((_b = pageProps === null || pageProps === void 0 ? void 0 : pageProps.metadata) === null || _b === void 0 ? void 0 : _b.type) === 'asciidoc';
76
+ // The page-type fallback is only for backward compatibility; templates set the scope.
77
+ const withPageActions = (_c = (0, react_1.useContext)(contexts_1.PageActionsContext)) !== null && _c !== void 0 ? _c : (((_a = pageProps === null || pageProps === void 0 ? void 0 : pageProps.metadata) === null || _a === void 0 ? void 0 : _a.type) === 'markdown' || ((_b = pageProps === null || pageProps === void 0 ? void 0 : pageProps.metadata) === null || _b === void 0 ? void 0 : _b.type) === 'asciidoc');
75
78
  const headingTextId = `${id}-heading-text`;
76
79
  const linkEl = (react_1.default.createElement("a", { "aria-labelledby": headingTextId, href: `#${id}`, className: (0, utils_1.concatClassNames)(constants_1.ANCHOR_CLASS, 'before') },
77
80
  react_1.default.createElement(LinkIcon_1.LinkIcon, { "aria-hidden": "true", focusable: "false", color: "--heading-anchor-color" })));
@@ -80,7 +83,7 @@ function Heading({ level, id, children, 'data-source': dataSource, 'data-hash':
80
83
  react_1.default.createElement(HeadingContentWrapper, null,
81
84
  linkEl,
82
85
  headingText,
83
- isMarkdownPage && __idx === 0 ? react_1.default.createElement(PageActions_1.PageActions, { pageSlug: pathname }) : null)));
86
+ withPageActions && __idx === 0 ? react_1.default.createElement(PageActions_1.PageActions, { pageSlug: pathname }) : null)));
84
87
  }
85
88
  const HeadingContentWrapper = styled_components_1.default.div `
86
89
  display: flex;
@@ -4,6 +4,9 @@ exports.accordionGroup = void 0;
4
4
  exports.accordionGroup = {
5
5
  schema: {
6
6
  render: 'AccordionGroup',
7
+ attributes: {
8
+ singleExpanded: { type: Boolean, default: false },
9
+ },
7
10
  },
8
11
  tagName: 'accordion-group',
9
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.69.0-next.8",
3
+ "version": "0.69.0",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -42,7 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@babel/core": "7.29.7",
44
44
  "@markdoc/markdoc": "0.5.2",
45
- "@redocly/realm-asyncapi-sdk": "0.15.0-next.3",
45
+ "@redocly/realm-asyncapi-sdk": "0.15.0",
46
46
  "@shikijs/types": "3.21.0",
47
47
  "@testing-library/react": "16.3.0",
48
48
  "@testing-library/user-event": "14.6.1",
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { BaseSyntheticEvent, PropsWithChildren, ReactNode, JSX } from 'react';
@@ -32,6 +32,12 @@ export function Accordion({
32
32
  }: PropsWithChildren<AccordionProps>): JSX.Element {
33
33
  const [isExpanded, setExpanded] = useState(expanded);
34
34
  const [animate, setAnimate] = useState(false);
35
+ const [prevExpanded, setPrevExpanded] = useState(expanded);
36
+
37
+ if (prevExpanded !== expanded) {
38
+ setPrevExpanded(expanded);
39
+ setExpanded(expanded);
40
+ }
35
41
 
36
42
  const toggle = ({ target }: BaseSyntheticEvent): void => {
37
43
  if (target instanceof HTMLAnchorElement || !isExpandable) return;
@@ -40,8 +46,6 @@ export function Accordion({
40
46
  onToggle?.(!isExpanded);
41
47
  };
42
48
 
43
- useEffect(() => setExpanded(expanded), [expanded]);
44
-
45
49
  return (
46
50
  <AccordionWrapper
47
51
  className={className}
@@ -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
 
@@ -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];
@@ -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
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -16,10 +16,12 @@ export function FilterInput(props: FilterInputProps): JSX.Element {
16
16
  const { value: initialValue, onChange, dataTestId } = props;
17
17
 
18
18
  const [value, setValue] = useState(initialValue);
19
+ const [prevInitialValue, setPrevInitialValue] = useState(initialValue);
19
20
 
20
- useEffect(() => {
21
+ if (prevInitialValue !== initialValue) {
22
+ setPrevInitialValue(initialValue);
21
23
  setValue(initialValue);
22
- }, [initialValue]);
24
+ }
23
25
 
24
26
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
25
27
  const newValue = e.target.value;
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from 'react';
1
+ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
@@ -23,14 +23,16 @@ export function JsonValue(props: {
23
23
  expandAllSignal ?? level < defaultExpandLevel,
24
24
  );
25
25
  const isExpanded = expandAllSignal !== undefined && level > 0 ? expandAllSignal : isExpandedState;
26
- useEffect(() => {
27
- if (expandAllSignal !== undefined) {
28
- if (expandAllSignal === false && level === 0) {
29
- return;
30
- }
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)) {
31
33
  setExpanded(expandAllSignal);
32
34
  }
33
- }, [expandAllSignal, level]);
35
+ }
34
36
 
35
37
  const keys: string[] =
36
38
  (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties
@@ -131,12 +133,14 @@ function JsonKeyValue(props: {
131
133
  expandAllSignal ?? level < defaultExpandLevel,
132
134
  );
133
135
  const isExpanded = expandAllSignal !== undefined ? expandAllSignal : isExpandedState;
136
+ const [prevExpandAllSignal, setPrevExpandAllSignal] = React.useState(expandAllSignal);
134
137
 
135
- useEffect(() => {
138
+ if (prevExpandAllSignal !== expandAllSignal) {
139
+ setPrevExpandAllSignal(expandAllSignal);
136
140
  if (expandAllSignal !== undefined) {
137
141
  setExpanded(expandAllSignal);
138
142
  }
139
- }, [expandAllSignal]);
143
+ }
140
144
 
141
145
  const openingPunctuation = Array.isArray(value) ? '[' : '{';
142
146
  const closingPunctuation = Array.isArray(value) ? ']' : '}';