@redocly/theme 0.57.0-next.0 → 0.57.0-next.2

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 (108) hide show
  1. package/lib/components/Catalog/CatalogCardView/CatalogCard.js +1 -0
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +36 -25
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.d.ts +6 -0
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.js +38 -0
  5. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.d.ts +6 -0
  6. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +83 -0
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.d.ts +2 -0
  8. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +29 -0
  9. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.d.ts +8 -0
  10. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.js +33 -0
  11. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.d.ts +16 -0
  12. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.js +24 -0
  13. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.d.ts +7 -0
  14. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.js +30 -0
  15. package/lib/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.js +5 -2
  16. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.d.ts +1 -0
  17. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +7 -3
  18. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.js +3 -8
  19. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.d.ts +22 -0
  20. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.js +18 -0
  21. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +2 -2
  22. package/lib/components/Catalog/CatalogEntityIcon.d.ts +2 -1
  23. package/lib/components/Catalog/CatalogEntityIcon.js +4 -6
  24. package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.js +1 -1
  25. package/lib/components/Catalog/variables.js +42 -0
  26. package/lib/components/CatalogClassic/CatalogClassicVirtualizedGroups.js +13 -8
  27. package/lib/core/constants/catalog.d.ts +10 -0
  28. package/lib/core/constants/catalog.js +14 -1
  29. package/lib/core/hooks/__mocks__/search/use-recent-searches.d.ts +2 -2
  30. package/lib/core/hooks/__mocks__/search/use-recent-searches.js +2 -1
  31. package/lib/core/hooks/__mocks__/search/use-search-filter.d.ts +2 -2
  32. package/lib/core/hooks/__mocks__/search/use-search-filter.js +2 -1
  33. package/lib/core/hooks/__mocks__/search/use-suggested-pages.d.ts +2 -2
  34. package/lib/core/hooks/__mocks__/search/use-suggested-pages.js +2 -1
  35. package/lib/core/hooks/__mocks__/use-controlled-state.d.ts +1 -1
  36. package/lib/core/hooks/__mocks__/use-controlled-state.js +2 -1
  37. package/lib/core/hooks/__mocks__/use-input-key-commands.d.ts +3 -3
  38. package/lib/core/hooks/__mocks__/use-input-key-commands.js +3 -2
  39. package/lib/core/hooks/__mocks__/use-mobile-menu.d.ts +1 -1
  40. package/lib/core/hooks/__mocks__/use-mobile-menu.js +2 -1
  41. package/lib/core/hooks/__mocks__/use-theme-config.d.ts +2 -2
  42. package/lib/core/hooks/__mocks__/use-theme-config.js +2 -1
  43. package/lib/core/hooks/__mocks__/use-theme-hooks.d.ts +47 -47
  44. package/lib/core/hooks/__mocks__/use-theme-hooks.js +38 -37
  45. package/lib/core/hooks/__mocks__/use-time-ago.d.ts +2 -2
  46. package/lib/core/hooks/__mocks__/use-time-ago.js +2 -1
  47. package/lib/core/hooks/catalog/useGraph.d.ts +15 -0
  48. package/lib/core/hooks/catalog/useGraph.js +165 -0
  49. package/lib/core/hooks/code-walkthrough/__mocks__/MockIntersectionObserver.js +4 -3
  50. package/lib/core/hooks/index.d.ts +1 -0
  51. package/lib/core/hooks/index.js +1 -0
  52. package/lib/core/hooks/menu/__mocks__/use-mobile-menu-items.d.ts +1 -1
  53. package/lib/core/hooks/menu/__mocks__/use-mobile-menu-items.js +2 -1
  54. package/lib/core/hooks/use-page-actions.js +5 -1
  55. package/lib/core/openapi/index.d.ts +1 -0
  56. package/lib/core/openapi/index.js +3 -1
  57. package/lib/core/styles/index.d.ts +1 -0
  58. package/lib/core/styles/index.js +3 -0
  59. package/lib/core/styles/xyflow.d.ts +1 -0
  60. package/lib/core/styles/xyflow.js +623 -0
  61. package/lib/core/utils/dynamic.d.ts +9 -0
  62. package/lib/core/utils/dynamic.js +59 -0
  63. package/lib/core/utils/index.d.ts +1 -0
  64. package/lib/core/utils/index.js +1 -0
  65. package/lib/icons/GenericIcon/GenericIcon.d.ts +2 -2
  66. package/lib/icons/GenericIcon/GenericIcon.js +8 -3
  67. package/lib/icons/__tests__/IconTestUtils.d.ts +0 -1
  68. package/lib/icons/__tests__/IconTestUtils.js +8 -7
  69. package/package.json +11 -14
  70. package/src/components/Catalog/CatalogCardView/CatalogCard.tsx +1 -0
  71. package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +60 -42
  72. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.tsx +63 -0
  73. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +7 -0
  74. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.tsx +91 -0
  75. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.tsx +48 -0
  76. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.tsx +45 -0
  77. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.tsx +49 -0
  78. package/src/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.tsx +6 -2
  79. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +8 -2
  80. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.tsx +24 -43
  81. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.tsx +76 -0
  82. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +2 -2
  83. package/src/components/Catalog/CatalogEntityIcon.tsx +7 -5
  84. package/src/components/Catalog/CatalogTableView/CatalogTableViewRow.tsx +1 -1
  85. package/src/components/Catalog/variables.ts +42 -0
  86. package/src/components/CatalogClassic/CatalogClassicVirtualizedGroups.tsx +29 -18
  87. package/src/core/constants/catalog.ts +13 -0
  88. package/src/core/hooks/__mocks__/search/use-recent-searches.ts +3 -1
  89. package/src/core/hooks/__mocks__/search/use-search-filter.ts +3 -1
  90. package/src/core/hooks/__mocks__/search/use-suggested-pages.ts +3 -1
  91. package/src/core/hooks/__mocks__/use-controlled-state.ts +3 -1
  92. package/src/core/hooks/__mocks__/use-input-key-commands.ts +4 -2
  93. package/src/core/hooks/__mocks__/use-mobile-menu.ts +3 -1
  94. package/src/core/hooks/__mocks__/use-theme-config.ts +3 -1
  95. package/src/core/hooks/__mocks__/use-theme-hooks.ts +39 -37
  96. package/src/core/hooks/__mocks__/use-time-ago.ts +3 -1
  97. package/src/core/hooks/catalog/useGraph.ts +236 -0
  98. package/src/core/hooks/code-walkthrough/__mocks__/MockIntersectionObserver.ts +5 -3
  99. package/src/core/hooks/index.ts +1 -0
  100. package/src/core/hooks/menu/__mocks__/use-mobile-menu-items.ts +3 -1
  101. package/src/core/hooks/use-page-actions.ts +5 -1
  102. package/src/core/openapi/index.ts +1 -0
  103. package/src/core/styles/index.ts +1 -0
  104. package/src/core/styles/xyflow.ts +620 -0
  105. package/src/core/utils/dynamic.tsx +85 -0
  106. package/src/core/utils/index.ts +1 -0
  107. package/src/icons/GenericIcon/GenericIcon.tsx +13 -4
  108. package/src/icons/__tests__/IconTestUtils.tsx +5 -4
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- export interface GenericIconProps {
2
+ export type GenericIconProps = {
3
3
  icon: string | React.ReactNode;
4
4
  srcSet?: string;
5
5
  rawContent?: string;
@@ -7,5 +7,5 @@ export interface GenericIconProps {
7
7
  color?: string;
8
8
  alt?: string;
9
9
  className?: string;
10
- }
10
+ };
11
11
  export declare function GenericIcon({ icon, srcSet, rawContent, size, color, alt, className, }: GenericIconProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
@@ -33,19 +33,24 @@ const CDNIcon_1 = require("../../icons/CDNIcon/CDNIcon");
33
33
  const utils_1 = require("../../core/utils");
34
34
  const InlineSvg_1 = require("../../markdoc/components/InlineSvg/InlineSvg");
35
35
  const Image_1 = require("../../components/Image/Image");
36
+ const INVALID_ICON_NAME = 'image-slash';
36
37
  function GenericIcon({ icon, srcSet, rawContent, size, color, alt, className, }) {
37
38
  if (srcSet) {
38
- return React.createElement(Image_1.Image, { srcSet: srcSet, alt: alt, className: className });
39
+ return React.createElement(IconSrcSetImg, { srcSet: srcSet, alt: alt, className: className });
39
40
  }
40
41
  const resolvedIcon = icon && typeof icon === 'string' ? (0, utils_1.resolveIcon)(icon) : null;
41
- const iconComponent = rawContent ? (React.createElement(IconSvg, { fileRawContent: rawContent, className: className })) : (resolvedIcon === null || resolvedIcon === void 0 ? void 0 : resolvedIcon.type) === 'link' ? (React.createElement(IconImg, { src: resolvedIcon.value, alt: alt, className: className })) : (resolvedIcon === null || resolvedIcon === void 0 ? void 0 : resolvedIcon.type) === 'font-awesome' ? (React.createElement(CDNIcon_1.CDNIcon, { name: resolvedIcon.name, type: resolvedIcon.style, size: size, color: color, className: className })) : (icon);
42
+ const iconComponent = rawContent ? (React.createElement(IconSvg, { fileRawContent: rawContent, className: className })) : (resolvedIcon === null || resolvedIcon === void 0 ? void 0 : resolvedIcon.type) === 'link' ? (React.createElement(IconImg, { src: resolvedIcon.value, alt: alt, className: className })) : (resolvedIcon === null || resolvedIcon === void 0 ? void 0 : resolvedIcon.type) === 'font-awesome' ? (React.createElement(CDNIcon_1.CDNIcon, { name: resolvedIcon.name, type: resolvedIcon.style, size: size, color: color, className: className })) : (resolvedIcon === null || resolvedIcon === void 0 ? void 0 : resolvedIcon.type) === 'invalid' ? (React.createElement(CDNIcon_1.CDNIcon, { name: INVALID_ICON_NAME, size: size, color: color, className: className })) : (icon);
42
43
  return iconComponent;
43
44
  }
44
45
  const IconImg = styled_components_1.default.img `
45
46
  width: var(--icon-width, 16px);
46
47
  height: var(--icon-height, 16px);
47
48
  display: inline-block;
48
- object-fit: cover;
49
+ vertical-align: middle;
50
+ `;
51
+ const IconSrcSetImg = (0, styled_components_1.default)(Image_1.Image) `
52
+ width: var(--icon-width, 16px);
53
+ height: var(--icon-height, 16px);
49
54
  `;
50
55
  const IconSvg = (0, styled_components_1.default)(InlineSvg_1.InlineSvg) `
51
56
  width: var(--icon-width, 16px);
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import '@testing-library/jest-dom';
3
2
  export declare function testIconComponent(IconComponent: React.ComponentType<any>, componentName: string): {
4
3
  rendersCorrectlyWithDefaultProps: () => void;
5
4
  appliesCustomSizeAndColor: () => void;
@@ -6,42 +6,43 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.testIconComponent = testIconComponent;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const react_2 = require("@testing-library/react");
9
- require("@testing-library/jest-dom");
9
+ const vitest_1 = require("vitest");
10
10
  function testIconComponent(IconComponent, componentName) {
11
11
  const dataName = `icons/${componentName}/${componentName}`;
12
12
  return {
13
13
  rendersCorrectlyWithDefaultProps: () => {
14
14
  const { container } = (0, react_2.render)(react_1.default.createElement(IconComponent, null));
15
15
  const svgElement = container.querySelector('svg');
16
- expect(svgElement).toBeInTheDocument();
16
+ (0, vitest_1.expect)(svgElement).toBeTruthy();
17
17
  // Check if styles are applied either via style attribute or CSS class
18
18
  const element = container.firstChild;
19
19
  const hasStyleAttr = element.style.height === '16px' && element.style.width === '16px';
20
20
  const elementClass = element.getAttribute('class') || '';
21
21
  const hasClassName = elementClass.includes('sc-'); // styled-components class
22
- expect(hasStyleAttr || hasClassName).toBeTruthy();
22
+ (0, vitest_1.expect)(hasStyleAttr || hasClassName).toBeTruthy();
23
23
  },
24
24
  appliesCustomSizeAndColor: () => {
25
25
  var _a;
26
26
  const { container } = (0, react_2.render)(react_1.default.createElement(IconComponent, { size: "24px", color: "--color-primary" }));
27
27
  const svgElement = container.querySelector('svg');
28
- expect(svgElement).toBeInTheDocument();
28
+ (0, vitest_1.expect)(svgElement).toBeTruthy();
29
29
  const element = container.firstChild;
30
30
  const pathElement = container.querySelector('path');
31
31
  // Check if size styles are applied either via style attribute or CSS class
32
32
  const hasSizeStyle = element.style.height === '24px' && element.style.width === '24px';
33
33
  const elementClass = element.getAttribute('class') || '';
34
34
  const hasClassName = elementClass.includes('sc-'); // styled-components class
35
- expect(hasSizeStyle || hasClassName).toBeTruthy();
35
+ (0, vitest_1.expect)(hasSizeStyle || hasClassName).toBeTruthy();
36
36
  // Check if color is applied either via style attribute or CSS class
37
37
  const hasColorStyle = ((_a = pathElement.getAttribute('style')) === null || _a === void 0 ? void 0 : _a.includes('fill: var(--color-primary)')) ||
38
38
  pathElement.getAttribute('fill') === 'var(--color-primary)' ||
39
39
  elementClass.includes('sc-'); // check styled-components class on the root element
40
- expect(hasColorStyle).toBeTruthy();
40
+ (0, vitest_1.expect)(hasColorStyle).toBeTruthy();
41
41
  },
42
42
  hasCorrectDataComponentName: () => {
43
43
  const { container } = (0, react_2.render)(react_1.default.createElement(IconComponent, null));
44
- expect(container.firstChild).toHaveAttribute('data-component-name', dataName);
44
+ (0, vitest_1.expect)(container.firstChild).toHaveProperty('getAttribute');
45
+ (0, vitest_1.expect)(container.firstChild.getAttribute('data-component-name')).toBe(dataName);
45
46
  },
46
47
  };
47
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.57.0-next.0",
3
+ "version": "0.57.0-next.2",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -38,13 +38,10 @@
38
38
  "devDependencies": {
39
39
  "@markdoc/markdoc": "0.5.2",
40
40
  "@shikijs/types": "1.24.2",
41
- "@testing-library/jest-dom": "6.6.3",
42
41
  "@testing-library/react": "16.3.0",
43
42
  "@testing-library/user-event": "14.6.1",
44
43
  "@types/file-saver": "2.0.7",
45
44
  "@types/highlight-words-core": "1.2.3",
46
- "@types/jest": "29.5.11",
47
- "@types/jest-when": "3.5.5",
48
45
  "@types/lodash.debounce": "4.0.9",
49
46
  "@types/lodash.throttle": "4.1.9",
50
47
  "@types/node": "22.15.3",
@@ -53,11 +50,9 @@
53
50
  "@types/react-dom": "^19.1.4",
54
51
  "@types/styled-components": "5.1.34",
55
52
  "@types/styled-system": "5.1.22",
53
+ "@vitest/coverage-v8": "^3.1.1",
54
+ "@vitest/ui": "3.2.4",
56
55
  "concurrently": "7.6.0",
57
- "jest": "29.5.0",
58
- "jest-environment-jsdom": "29.5.0",
59
- "jest-styled-components": "7.2.0",
60
- "jest-when": "3.6.0",
61
56
  "json-schema-to-ts": "2.7.2",
62
57
  "npm-run-all2": "5.0.2",
63
58
  "react-refresh": "0.14.2",
@@ -66,18 +61,20 @@
66
61
  "rimraf": "5.0.7",
67
62
  "styled-components": "5.3.11",
68
63
  "styled-system": "5.1.5",
69
- "ts-jest": "29.1.2",
70
64
  "ts-node": "10.9.2",
71
65
  "ts-node-dev": "2.0.0",
72
66
  "tsc-alias": "1.8.10",
73
67
  "tsconfig-paths": "4.2.0",
74
68
  "tsconfig-paths-webpack-plugin": "3.5.2",
75
69
  "typescript": "5.6.2",
70
+ "vitest": "3.2.4",
71
+ "vitest-when": "0.6.2",
76
72
  "webpack": "5.94.0"
77
73
  },
78
74
  "dependencies": {
79
75
  "@tanstack/react-query": "5.62.3",
80
76
  "@tanstack/react-virtual": "3.13.0",
77
+ "@xyflow/react": "^12.8.2",
81
78
  "copy-to-clipboard": "3.3.3",
82
79
  "file-saver": "2.0.5",
83
80
  "highlight-words-core": "1.2.2",
@@ -98,10 +95,10 @@
98
95
  "clean": "rimraf lib",
99
96
  "compile": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
100
97
  "build": "npm run clean && npm run compile",
101
- "test": "jest",
102
- "test:update": "jest -u",
103
- "test:watch": "jest --watch",
104
- "test:coverage": "jest --coverage",
105
- "test:coverage:html": "jest --coverage --coverageReporters html"
98
+ "test": "vitest run",
99
+ "test:update": "vitest run --update",
100
+ "test:watch": "vitest",
101
+ "test:coverage": "vitest run --coverage",
102
+ "test:coverage:html": "vitest run --coverage --reporter=html"
106
103
  }
107
104
  }
@@ -166,6 +166,7 @@ const ArrowCircle = styled.div`
166
166
 
167
167
  const CardContent = styled.div`
168
168
  padding: var(--catalog-card-padding-vertical) var(--catalog-card-padding-horizontal);
169
+ height: 100%;
169
170
  `;
170
171
 
171
172
  const CardFooter = styled.div`
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { CatalogEntityConfig, EntitiesCatalogConfig } from '@redocly/config';
4
+ import { Route, Routes, useLocation, matchPath } from 'react-router-dom';
4
5
 
5
6
  import type { BffCatalogEntity, BffCatalogRelatedEntityList } from '@redocly/theme/core/types';
6
7
 
@@ -19,6 +20,7 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
19
20
  import { SidebarActions } from '@redocly/theme/components/SidebarActions/SidebarActions';
20
21
  import { CatalogEntitySchema } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntitySchema';
21
22
  import { CatalogEntityMethodAndPath } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath';
23
+ import { CatalogEntityRelationsGraph } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy';
22
24
 
23
25
  export type CatalogEntityProps = {
24
26
  entity: BffCatalogEntity;
@@ -27,6 +29,17 @@ export type CatalogEntityProps = {
27
29
  initialRelations: BffCatalogRelatedEntityList;
28
30
  };
29
31
 
32
+ const renderEntityContent = (entity: BffCatalogEntity): React.ReactElement => {
33
+ switch (entity.type) {
34
+ case 'data-schema':
35
+ return <CatalogEntitySchema entity={entity} />;
36
+ case 'api-operation':
37
+ return <CatalogEntityMethodAndPath entity={entity} />;
38
+ default:
39
+ return <CatalogEntityMetadata entity={entity} />;
40
+ }
41
+ };
42
+
30
43
  export function CatalogEntity({
31
44
  entity,
32
45
  entitiesCatalogConfig,
@@ -37,7 +50,6 @@ export function CatalogEntity({
37
50
  const { translate } = useTranslate();
38
51
  const linkToMainCatalog = `catalogs/${catalogConfig.slug}`;
39
52
  const linkToMainCatalogLabel = translate(catalogConfig.titleTranslationKey);
40
-
41
53
  const {
42
54
  layout,
43
55
  collapsedSidebar,
@@ -47,16 +59,19 @@ export function CatalogEntity({
47
59
  setSearchQuery,
48
60
  } = useCatalog();
49
61
 
50
- const renderEntityContent = (): React.ReactElement => {
51
- switch (entity.type) {
52
- case 'data-schema':
53
- return <CatalogEntitySchema entity={entity} />;
54
- case 'api-operation':
55
- return <CatalogEntityMethodAndPath entity={entity} />;
56
- default:
57
- return <CatalogEntityMetadata entity={entity} />;
58
- }
59
- };
62
+ const { pathname } = useLocation();
63
+ const linkBase = `${linkToMainCatalog}/entities/${entity.key}`;
64
+ const menuItemDefs = [
65
+ { label: 'Overview', path: linkBase, end: true },
66
+ { label: 'Relations Graph', path: `${linkBase}/relations-graph`, end: true },
67
+ ];
68
+ const sidebarMenuItems = menuItemDefs.map(({ label, path, end }) => ({
69
+ label,
70
+ link: path,
71
+ active: Boolean(matchPath({ path: `/${path}`, end }, pathname)),
72
+ items: [],
73
+ hasActiveSubItem: false,
74
+ }));
60
75
 
61
76
  return (
62
77
  <CatalogPageWrapper data-component-name="Catalog/CatalogEntity/CatalogEntity">
@@ -80,19 +95,7 @@ export function CatalogEntity({
80
95
  )}
81
96
  </BackLink>
82
97
  }
83
- menu={
84
- <Menu
85
- items={[
86
- {
87
- label: 'Overview',
88
- link: `${linkToMainCatalog}/entities/${entity.key}`,
89
- active: true,
90
- items: [],
91
- hasActiveSubItem: false,
92
- },
93
- ]}
94
- />
95
- }
98
+ menu={<Menu items={sidebarMenuItems} />}
96
99
  footer={
97
100
  <SidebarActions
98
101
  layout={layout}
@@ -111,24 +114,39 @@ export function CatalogEntity({
111
114
  ]}
112
115
  />
113
116
  <CatalogEntityPageWrapper>
114
- <CatalogPageDescription
115
- title={entity.title}
116
- description={entity.summary ?? ''}
117
- tag={entity.key}
118
- />
119
- <CatalogEntityProperties entity={entity} />
120
- <CatalogTwoColumnsSection>
121
- {renderEntityContent()}
122
- <CatalogEntityLinks entity={entity} />
123
- </CatalogTwoColumnsSection>
124
- <CatalogEntityRelations
125
- entity={entity}
126
- entitiesCatalogConfig={entitiesCatalogConfig}
127
- catalogConfig={catalogConfig}
128
- initialRelations={initialRelations}
129
- searchQuery={searchQuery}
130
- setSearchQuery={setSearchQuery}
131
- />
117
+ <Routes>
118
+ <Route path={`${catalogConfig.slug}/entities/${entity.key}`}>
119
+ <Route
120
+ index
121
+ element={
122
+ <>
123
+ <CatalogPageDescription
124
+ title={entity.title}
125
+ description={entity.summary ?? ''}
126
+ tag={entity.key}
127
+ />
128
+ <CatalogEntityProperties entity={entity} />
129
+ <CatalogTwoColumnsSection>
130
+ {renderEntityContent(entity)}
131
+ <CatalogEntityLinks entity={entity} />
132
+ </CatalogTwoColumnsSection>
133
+ <CatalogEntityRelations
134
+ entity={entity}
135
+ entitiesCatalogConfig={entitiesCatalogConfig}
136
+ catalogConfig={catalogConfig}
137
+ initialRelations={initialRelations}
138
+ searchQuery={searchQuery}
139
+ setSearchQuery={setSearchQuery}
140
+ />
141
+ </>
142
+ }
143
+ />
144
+ <Route
145
+ path="relations-graph"
146
+ element={<CatalogEntityRelationsGraph entity={entity} />}
147
+ />
148
+ </Route>
149
+ </Routes>
132
150
  </CatalogEntityPageWrapper>
133
151
  </CatalogPageContent>
134
152
  </CatalogPageWrapper>
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath, type EdgeProps } from '@xyflow/react';
3
+ import styled from 'styled-components';
4
+
5
+ export type CatalogEntityRelationsEdgeProps = EdgeProps & { className?: string };
6
+
7
+ export function CatalogEntityRelationsEdge(
8
+ props: CatalogEntityRelationsEdgeProps,
9
+ ): React.ReactElement {
10
+ const {
11
+ id,
12
+ sourceX,
13
+ sourceY,
14
+ sourcePosition,
15
+ targetX,
16
+ targetY,
17
+ targetPosition,
18
+ style,
19
+ label,
20
+ markerEnd,
21
+ className,
22
+ } = props;
23
+
24
+ const [edgePath, labelX, labelY] = getSmoothStepPath({
25
+ sourceX,
26
+ sourceY,
27
+ sourcePosition,
28
+ targetX,
29
+ targetY,
30
+ targetPosition,
31
+ borderRadius: 12,
32
+ });
33
+
34
+ return (
35
+ <>
36
+ <BaseEdge id={id} path={edgePath} style={style} markerEnd={markerEnd} />
37
+ {label ? (
38
+ <EdgeLabelRenderer>
39
+ <EdgeLabel
40
+ className={className ? `nopan ${className}` : 'nopan'}
41
+ style={{ transform: `translate(-50%, 0%) translate(${labelX}px, ${labelY - 10}px)` }}
42
+ data-component-name="Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge"
43
+ >
44
+ {label}
45
+ </EdgeLabel>
46
+ </EdgeLabelRenderer>
47
+ ) : null}
48
+ </>
49
+ );
50
+ }
51
+
52
+ const EdgeLabel = styled.div`
53
+ position: absolute;
54
+ background: var(--catalog-entity-relations-edge-label-bg-color);
55
+ color: var(--catalog-entity-relations-edge-label-text-color);
56
+ border: var(--catalog-entity-relations-edge-label-border-width)
57
+ var(--catalog-entity-relations-edge-label-border-style)
58
+ var(--catalog-entity-relations-edge-label-border-color);
59
+ border-radius: var(--catalog-entity-relations-edge-label-border-radius);
60
+ padding: var(--catalog-entity-relations-edge-label-padding);
61
+ font-size: var(--catalog-entity-relations-edge-label-font-size);
62
+ line-height: var(--catalog-entity-relations-edge-label-line-height);
63
+ `;
@@ -0,0 +1,7 @@
1
+ import type { CatalogEntityRelationsGraphProps } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph';
2
+
3
+ import { dynamic } from '@redocly/theme/core/utils';
4
+
5
+ export const CatalogEntityRelationsGraph = dynamic<CatalogEntityRelationsGraphProps>(() =>
6
+ import('./CatalogEntityRelationsGraph').then((mod) => mod.CatalogEntityRelationsGraph),
7
+ );
@@ -0,0 +1,91 @@
1
+ import React, { JSX, useMemo } from 'react';
2
+ import { Background, ColorMode, Controls, ReactFlow } from '@xyflow/react';
3
+ import styled from 'styled-components';
4
+
5
+ import { BffCatalogEntity } from '@redocly/theme/core/types';
6
+ import { useColorSwitcher, useThemeHooks } from '@redocly/theme/core/hooks';
7
+ import { useGraph } from '@redocly/theme/core/hooks/catalog/useGraph';
8
+ import { CatalogEntityRelationsNode } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode';
9
+ import { CatalogEntityRelationsEdge } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge';
10
+ import { xyflow } from '@redocly/theme/core/styles';
11
+ import { CatalogPageDescription } from '@redocly/theme/components/Catalog/CatalogPageDescription';
12
+ import { GraphCustomEdgeType, GraphCustomNodeType } from '@redocly/theme/core/constants/catalog';
13
+
14
+ export type CatalogEntityRelationsGraphProps = {
15
+ entity: BffCatalogEntity;
16
+ };
17
+
18
+ const commonReactFlowConfig = {
19
+ fitView: true,
20
+ proOptions: {
21
+ hideAttribution: true,
22
+ },
23
+ style: {
24
+ border:
25
+ 'var(--catalog-card-border-width) var(--catalog-card-border-style) var(--catalog-card-border-color)',
26
+ borderRadius: 'var(--catalog-card-border-radius, 8px)',
27
+ },
28
+ };
29
+
30
+ export function CatalogEntityRelationsGraph({
31
+ entity,
32
+ }: CatalogEntityRelationsGraphProps): JSX.Element {
33
+ const { useFetchCatalogEntitiesRelations } = useThemeHooks();
34
+ const { activeColorMode } = useColorSwitcher();
35
+ const { items: allRelations } = useFetchCatalogEntitiesRelations({
36
+ entityKey: entity.key,
37
+ });
38
+
39
+ const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useGraph({
40
+ entity,
41
+ relations: allRelations ?? [],
42
+ });
43
+
44
+ const nodeTypes = useMemo(
45
+ () => ({ [GraphCustomNodeType.CatalogEntity]: CatalogEntityRelationsNode }),
46
+ [],
47
+ );
48
+ const edgeTypes = useMemo(
49
+ () => ({ [GraphCustomEdgeType.CatalogEdge]: CatalogEntityRelationsEdge }),
50
+ [],
51
+ );
52
+
53
+ const reactFlowConfig = allRelations.length
54
+ ? {
55
+ key: entity.id,
56
+ nodes,
57
+ edges,
58
+ nodeTypes,
59
+ edgeTypes,
60
+ onNodesChange,
61
+ onEdgesChange,
62
+ onConnect,
63
+ colorMode: activeColorMode as ColorMode,
64
+ ...commonReactFlowConfig,
65
+ }
66
+ : {
67
+ ...commonReactFlowConfig,
68
+ };
69
+
70
+ return (
71
+ <>
72
+ <CatalogPageDescription
73
+ title={entity.title}
74
+ description={entity.summary ?? ''}
75
+ tag={entity.key}
76
+ />
77
+ <GraphWrapper>
78
+ <ReactFlow {...reactFlowConfig}>
79
+ <Background bgColor="var(--catalog-card-bg-color)" />
80
+ <Controls />
81
+ </ReactFlow>
82
+ </GraphWrapper>
83
+ </>
84
+ );
85
+ }
86
+
87
+ const GraphWrapper = styled.div`
88
+ height: 700px;
89
+ width: 100%;
90
+ ${xyflow}
91
+ `;
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { Handle, Position } from '@xyflow/react';
3
+ import styled from 'styled-components';
4
+
5
+ import { CatalogEntityIcon } from '@redocly/theme/components/Catalog/CatalogEntityIcon';
6
+ import { Link } from '@redocly/theme/components/Link/Link';
7
+ import { GraphHandleType } from '@redocly/theme/core/constants/catalog';
8
+
9
+ export type CatalogEntityRelationsLinkedNodeProps = {
10
+ label: string;
11
+ entityType: string;
12
+ className?: string;
13
+ to: string;
14
+ };
15
+
16
+ export function CatalogEntityRelationsLinkedNode({
17
+ label,
18
+ entityType,
19
+ className,
20
+ to,
21
+ }: CatalogEntityRelationsLinkedNodeProps): React.ReactElement {
22
+ return (
23
+ <RelationsLinkedNodeWrapper
24
+ className={className}
25
+ to={to}
26
+ data-component-name="Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode"
27
+ >
28
+ <Handle type={GraphHandleType.Target} position={Position.Top} id={GraphHandleType.Target} />
29
+ <CatalogEntityIcon entityType={entityType} defaultColor={false} />
30
+ <div>{label}</div>
31
+ </RelationsLinkedNodeWrapper>
32
+ );
33
+ }
34
+
35
+ const RelationsLinkedNodeWrapper = styled(Link)`
36
+ display: flex;
37
+ align-items: center;
38
+ gap: var(--catalog-entity-relations-node-gap);
39
+ padding: var(--catalog-entity-relations-node-padding);
40
+ border-radius: var(--catalog-entity-relations-node-border-radius);
41
+ font-style: normal;
42
+ font-weight: var(--catalog-entity-relations-node-font-weight);
43
+ background: var(--catalog-entity-relations-node-bg-color);
44
+ color: var(--catalog-entity-relations-node-text-color);
45
+ border: var(--catalog-entity-relations-node-border-width)
46
+ var(--catalog-entity-relations-node-border-style)
47
+ var(--catalog-entity-relations-node-border-color);
48
+ `;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+
3
+ import { CatalogEntityRelationsRootNode } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode';
4
+ import { CatalogEntityRelationsLinkedNode } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode';
5
+
6
+ export type CatalogEntityNodeData = {
7
+ label: string;
8
+ entityType: string;
9
+ entityKey: string;
10
+ isRoot?: boolean;
11
+ };
12
+
13
+ export type CatalogEntityRelationsNodeProps = { data: CatalogEntityNodeData; className?: string };
14
+
15
+ export enum HandleType {
16
+ Target = 'target',
17
+ Source = 'source',
18
+ }
19
+
20
+ export function CatalogEntityRelationsNode({
21
+ data,
22
+ className,
23
+ }: CatalogEntityRelationsNodeProps): React.ReactElement {
24
+ const { label, entityType, isRoot, entityKey } = data;
25
+ const mergedClassName = `nopan${className ? ` ${className}` : ''}`;
26
+
27
+ if (isRoot) {
28
+ return (
29
+ <CatalogEntityRelationsRootNode
30
+ className={mergedClassName}
31
+ label={label}
32
+ entityType={entityType}
33
+ />
34
+ );
35
+ }
36
+
37
+ return (
38
+ <CatalogEntityRelationsLinkedNode
39
+ className={mergedClassName}
40
+ label={label}
41
+ entityType={entityType}
42
+ to={`/catalogs/all/entities/${entityKey}`}
43
+ />
44
+ );
45
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Handle, Position } from '@xyflow/react';
3
+ import styled from 'styled-components';
4
+
5
+ import { CatalogEntityIcon } from '@redocly/theme/components/Catalog/CatalogEntityIcon';
6
+ import { GraphHandleType } from '@redocly/theme/core/constants/catalog';
7
+
8
+ export type CatalogEntityRelationsRootNodeProps = {
9
+ label: string;
10
+ entityType: string;
11
+ className?: string;
12
+ };
13
+
14
+ export function CatalogEntityRelationsRootNode({
15
+ label,
16
+ entityType,
17
+ className,
18
+ }: CatalogEntityRelationsRootNodeProps): React.ReactElement {
19
+ return (
20
+ <RelationsRootNodeWrapper
21
+ className={className}
22
+ data-component-name="Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode"
23
+ >
24
+ <CatalogEntityIcon
25
+ entityType={entityType}
26
+ forceColor="var(--catalog-entity-relations-node-root-icon-color)"
27
+ />
28
+ <div>{label}</div>
29
+ <Handle
30
+ type={GraphHandleType.Source}
31
+ position={Position.Bottom}
32
+ id={GraphHandleType.Source}
33
+ />
34
+ </RelationsRootNodeWrapper>
35
+ );
36
+ }
37
+
38
+ const RelationsRootNodeWrapper = styled.div`
39
+ display: flex;
40
+ align-items: center;
41
+ gap: var(--catalog-entity-relations-node-gap);
42
+ padding: var(--catalog-entity-relations-node-padding);
43
+ border-radius: var(--catalog-entity-relations-node-border-radius);
44
+ font-style: normal;
45
+ font-weight: var(--catalog-entity-relations-node-font-weight-root);
46
+ background: var(--catalog-entity-relations-node-root-bg-color);
47
+ color: var(--catalog-entity-relations-node-root-text-color);
48
+ border: none;
49
+ `;