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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/lib/components/Catalog/Catalog.d.ts +2 -2
  2. package/lib/components/Catalog/Catalog.js +6 -4
  3. package/lib/components/Catalog/CatalogCardView/CatalogCard.js +15 -14
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntity.d.ts +5 -1
  5. package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +4 -4
  6. package/lib/components/Catalog/CatalogEntity/CatalogEntityLinks.js +0 -1
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +1 -2
  8. package/lib/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.js +0 -1
  9. package/lib/components/Catalog/CatalogEntity/CatalogEntityProperties/CatalogEntityPropertyCard.js +1 -1
  10. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
  11. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.d.ts +5 -1
  12. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +9 -7
  13. package/lib/components/Catalog/CatalogEntityIcon.d.ts +2 -0
  14. package/lib/components/Catalog/CatalogEntityIcon.js +31 -14
  15. package/lib/components/Catalog/CatalogEntityTypeIcon.js +19 -6
  16. package/lib/components/Catalog/CatalogEntityTypeTag.js +9 -3
  17. package/lib/components/Catalog/CatalogSelector.d.ts +1 -1
  18. package/lib/components/Catalog/CatalogTableView/CatalogEntityCell.js +1 -1
  19. package/lib/components/Catalog/CatalogViewModeToggle.d.ts +1 -1
  20. package/lib/components/Catalog/variables.js +9 -6
  21. package/lib/components/CatalogClassic/CatalogClassic.js +9 -2
  22. package/lib/components/CodeBlock/CodeBlock.d.ts +5 -12
  23. package/lib/components/CodeBlock/CodeBlockControls.d.ts +3 -3
  24. package/lib/components/CodeBlock/CodeBlockDropdown.d.ts +2 -2
  25. package/lib/components/CodeBlock/CodeBlockDropdown.js +4 -13
  26. package/lib/components/CodeBlock/CodeBlockTabs.d.ts +2 -2
  27. package/lib/components/CodeBlock/CodeBlockTabs.js +4 -3
  28. package/lib/components/Search/SearchDialog.js +12 -6
  29. package/lib/components/Search/SearchFilter.js +2 -1
  30. package/lib/components/Tooltip/Tooltip.js +7 -9
  31. package/lib/components/Tooltip/TooltipWrapper.js +1 -1
  32. package/lib/core/constants/catalog.d.ts +1 -1
  33. package/lib/core/constants/catalog.js +13 -27
  34. package/lib/core/contexts/CodeSnippetContext.d.ts +14 -6
  35. package/lib/core/contexts/CodeSnippetContext.js +57 -14
  36. package/lib/core/hooks/catalog/useCatalogTableViewRow.js +1 -1
  37. package/lib/core/hooks/use-active-section-id.js +4 -0
  38. package/lib/core/hooks/use-codeblock-tabs-controls.d.ts +2 -2
  39. package/lib/core/hooks/use-control.js +17 -2
  40. package/lib/core/hooks/use-local-state.js +22 -18
  41. package/lib/core/hooks/use-telemetry-fallback.d.ts +5 -0
  42. package/lib/core/hooks/use-telemetry-fallback.js +5 -0
  43. package/lib/core/openapi/index.d.ts +8 -4
  44. package/lib/core/openapi/index.js +9 -9
  45. package/lib/core/styles/global.js +19 -0
  46. package/lib/core/types/catalog.d.ts +1 -1
  47. package/lib/core/types/hooks.d.ts +2 -2
  48. package/lib/core/types/index.d.ts +1 -0
  49. package/lib/core/types/index.js +1 -0
  50. package/lib/core/types/l10n.d.ts +1 -1
  51. package/lib/core/types/open-api-info.d.ts +34 -0
  52. package/lib/core/types/open-api-info.js +3 -0
  53. package/lib/core/types/open-api-server.d.ts +1 -0
  54. package/lib/core/types/search.d.ts +2 -3
  55. package/lib/core/utils/urls.js +1 -1
  56. package/lib/ext/useConfigureReplay.d.ts +2 -1
  57. package/lib/icons/HierarchyIcon/HierarchyIcon.d.ts +9 -0
  58. package/lib/icons/HierarchyIcon/HierarchyIcon.js +23 -0
  59. package/lib/icons/NoteIcon/NoteIcon.d.ts +9 -0
  60. package/lib/icons/NoteIcon/NoteIcon.js +24 -0
  61. package/lib/icons/ShareIcon/ShareIcon.d.ts +9 -0
  62. package/lib/icons/ShareIcon/ShareIcon.js +22 -0
  63. package/lib/index.d.ts +2 -0
  64. package/lib/index.js +2 -0
  65. package/lib/layouts/DocumentationLayout.js +1 -3
  66. package/lib/markdoc/components/CodeGroup/CodeGroup.js +49 -27
  67. package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -1
  68. package/lib/markdoc/components/Tabs/Tabs.js +3 -2
  69. package/package.json +4 -4
  70. package/src/components/Catalog/Catalog.tsx +18 -6
  71. package/src/components/Catalog/CatalogCardView/CatalogCard.tsx +20 -19
  72. package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +15 -2
  73. package/src/components/Catalog/CatalogEntity/CatalogEntityLinks.tsx +0 -1
  74. package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +1 -2
  75. package/src/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.tsx +0 -1
  76. package/src/components/Catalog/CatalogEntity/CatalogEntityProperties/CatalogEntityPropertyCard.tsx +1 -1
  77. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
  78. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +31 -18
  79. package/src/components/Catalog/CatalogEntityIcon.tsx +53 -18
  80. package/src/components/Catalog/CatalogEntityTypeIcon.tsx +19 -8
  81. package/src/components/Catalog/CatalogEntityTypeTag.tsx +11 -3
  82. package/src/components/Catalog/CatalogSelector.tsx +1 -1
  83. package/src/components/Catalog/CatalogTableView/CatalogEntityCell.tsx +1 -1
  84. package/src/components/Catalog/CatalogViewModeToggle.tsx +1 -1
  85. package/src/components/Catalog/variables.ts +9 -6
  86. package/src/components/CatalogClassic/CatalogClassic.tsx +26 -10
  87. package/src/components/CodeBlock/CodeBlock.tsx +5 -11
  88. package/src/components/CodeBlock/CodeBlockControls.tsx +3 -6
  89. package/src/components/CodeBlock/CodeBlockDropdown.tsx +11 -20
  90. package/src/components/CodeBlock/CodeBlockTabs.tsx +8 -8
  91. package/src/components/Search/SearchDialog.tsx +14 -5
  92. package/src/components/Search/SearchFilter.tsx +2 -1
  93. package/src/components/Tooltip/Tooltip.tsx +6 -8
  94. package/src/components/Tooltip/TooltipWrapper.tsx +1 -1
  95. package/src/core/constants/catalog.ts +13 -27
  96. package/src/core/contexts/CodeSnippetContext.tsx +54 -18
  97. package/src/core/hooks/catalog/useCatalogTableViewRow.ts +1 -1
  98. package/src/core/hooks/use-active-section-id.ts +6 -0
  99. package/src/core/hooks/use-codeblock-tabs-controls.ts +2 -2
  100. package/src/core/hooks/use-control.ts +21 -3
  101. package/src/core/hooks/use-local-state.ts +28 -19
  102. package/src/core/hooks/use-telemetry-fallback.ts +5 -0
  103. package/src/core/openapi/index.ts +8 -4
  104. package/src/core/styles/global.ts +19 -0
  105. package/src/core/types/catalog.ts +1 -2
  106. package/src/core/types/hooks.ts +6 -1
  107. package/src/core/types/index.ts +1 -0
  108. package/src/core/types/l10n.ts +3 -0
  109. package/src/core/types/open-api-info.ts +34 -0
  110. package/src/core/types/open-api-server.ts +1 -0
  111. package/src/core/types/search.ts +3 -3
  112. package/src/core/utils/urls.ts +2 -1
  113. package/src/ext/useConfigureReplay.ts +2 -1
  114. package/src/icons/HierarchyIcon/HierarchyIcon.tsx +32 -0
  115. package/src/icons/NoteIcon/NoteIcon.tsx +35 -0
  116. package/src/icons/ShareIcon/ShareIcon.tsx +23 -0
  117. package/src/index.ts +2 -0
  118. package/src/layouts/DocumentationLayout.tsx +3 -10
  119. package/src/markdoc/components/CodeGroup/CodeGroup.tsx +81 -52
  120. package/src/markdoc/components/Tabs/Tabs.tsx +10 -2
@@ -15,7 +15,8 @@ export type OperationParameter = {
15
15
  enum: string[] | undefined;
16
16
  };
17
17
 
18
- export type SearchDocument = {
18
+ // TODO: review SearchHit type as it might contain properties that are not in the search results
19
+ export type SearchHit = {
19
20
  id: string;
20
21
  url: string;
21
22
  title: string | string[];
@@ -26,7 +27,6 @@ export type SearchDocument = {
26
27
  deprecated?: boolean;
27
28
  security?: string[];
28
29
  parameters?: OperationParameter[];
29
- metadata?: Record<string, any>;
30
30
  version?: string;
31
31
  versionFolderId?: string;
32
32
  isDefaultVersion?: boolean;
@@ -47,7 +47,7 @@ export type ParameterHighlight = {
47
47
  };
48
48
 
49
49
  export type SearchItemData = {
50
- document: SearchDocument;
50
+ document: SearchHit;
51
51
  highlight: Record<string, string> & { parameters?: ParameterHighlight[]; path?: string[] };
52
52
  };
53
53
 
@@ -54,8 +54,9 @@ export function addLeadingSlash(url: string): string {
54
54
  }
55
55
 
56
56
  export function removeTrailingSlash(url: string): string {
57
- return url.endsWith('/') ? url.substring(0, url.length - 1) : url;
57
+ return url.endsWith('/') && url !== '/' ? url.substring(0, url.length - 1) : url;
58
58
  }
59
+
59
60
  export function removeLeadingSlash(url: string): string {
60
61
  return url.startsWith('/') ? url.substring(1) : url;
61
62
  }
@@ -4,7 +4,7 @@ import type {
4
4
  ConfigureRequestValues,
5
5
  ConfigureServerRequestValues,
6
6
  } from '@redocly/theme/ext/configure';
7
- import type { UserClaims, OpenAPIServer } from '@redocly/theme/core/types';
7
+ import type { UserClaims, OpenAPIServer, OpenAPIInfo } from '@redocly/theme/core/types';
8
8
 
9
9
  type ContextProps = {
10
10
  operation: {
@@ -14,6 +14,7 @@ type ContextProps = {
14
14
  href: string;
15
15
  method: string;
16
16
  };
17
+ info: OpenAPIInfo;
17
18
  servers: OpenAPIServer[];
18
19
  userClaims: UserClaims;
19
20
  };
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" {...props}>
10
+ <path
11
+ fillRule="evenodd"
12
+ clipRule="evenodd"
13
+ d="M5.0625 7.3125H15.1875C15.4858 7.31216 15.7717 7.19352 15.9826 6.98262C16.1935 6.77171 16.3122 6.48576 16.3125 6.1875V2.8125C16.3122 2.51424 16.1935 2.22829 15.9826 2.01738C15.7717 1.80648 15.4858 1.68784 15.1875 1.6875H2.8125C2.51424 1.68784 2.22829 1.80648 2.01738 2.01738C1.80648 2.22829 1.68784 2.51424 1.6875 2.8125V6.1875C1.68784 6.48576 1.80648 6.77171 2.01738 6.98262C2.22829 7.19352 2.51424 7.31216 2.8125 7.3125H3.9375H5.0625ZM15.1875 2.8125H2.8125V6.1875H15.1875V2.8125Z"
14
+ />
15
+ <path
16
+ fillRule="evenodd"
17
+ clipRule="evenodd"
18
+ d="M15.1875 10.6875H9.5625C9.26424 10.6878 8.97829 10.8065 8.76738 11.0174C8.55648 11.2283 8.43784 11.5142 8.4375 11.8125V12.9375H5.0625V10.4546V9.5625V7.3125H3.9375V10.4546V12.9375C3.93784 13.2358 4.05648 13.5217 4.26738 13.7326C4.47829 13.9435 4.76424 14.0622 5.0625 14.0625H8.4375V15.1875C8.43784 15.4858 8.55648 15.7717 8.76738 15.9826C8.97829 16.1935 9.26424 16.3122 9.5625 16.3125H15.1875C15.4858 16.3122 15.7717 16.1935 15.9826 15.9826C16.1935 15.7717 16.3122 15.4858 16.3125 15.1875V11.8125C16.3122 11.5142 16.1935 11.2283 15.9826 11.0174C15.7717 10.8065 15.4858 10.6878 15.1875 10.6875ZM9.5625 11.8125V15.1875H15.1875V11.8125H9.5625Z"
19
+ />
20
+ </svg>
21
+ );
22
+
23
+ export const HierarchyIcon = styled(Icon).attrs(() => ({
24
+ 'data-component-name': 'icons/HierarchyIcon/HierarchyIcon',
25
+ }))<IconProps>`
26
+ path {
27
+ fill: ${({ color }) => getCssColorVariable(color)};
28
+ }
29
+
30
+ height: ${({ size }) => size || '16px'};
31
+ width: ${({ size }) => size || '16px'};
32
+ `;
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ width="8"
12
+ height="12"
13
+ viewBox="0 0 8 12"
14
+ fill="none"
15
+ {...props}
16
+ >
17
+ <path
18
+ d="M7.6375 3.4875L5.0125 0.8625C4.9375 0.7875 4.8625 0.75 4.75 0.75H1C0.5875 0.75 0.25 1.0875 0.25 1.5V10.5C0.25 10.9125 0.5875 11.25 1 11.25H7C7.4125 11.25 7.75 10.9125 7.75 10.5V3.75C7.75 3.6375 7.7125 3.5625 7.6375 3.4875ZM4.75 1.65L6.85 3.75H4.75V1.65ZM7 10.5H1V1.5H4V3.75C4 4.1625 4.3375 4.5 4.75 4.5H7V10.5Z"
19
+ fill="currentColor"
20
+ />
21
+ <path d="M6.25 8.25H1.75V9H6.25V8.25Z" fill="currentColor" />
22
+ <path d="M6.25 6H1.75V6.75H6.25V6Z" fill="currentColor" />
23
+ </svg>
24
+ );
25
+
26
+ export const NoteIcon = styled(Icon).attrs(() => ({
27
+ 'data-component-name': 'icons/NoteIcon/NoteIcon',
28
+ }))<IconProps>`
29
+ path {
30
+ fill: ${({ color }) => getCssColorVariable(color)};
31
+ }
32
+
33
+ height: ${({ size }) => size || '16px'};
34
+ width: ${({ size }) => size || '16px'};
35
+ `;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path d="M11.5 10.0001C11.1257 10.002 10.7566 10.0879 10.42 10.2514C10.0834 10.415 9.78776 10.652 9.55497 10.9451L5.89997 8.6601C6.03318 8.23018 6.03318 7.77002 5.89997 7.3401L9.55497 5.0551C9.92594 5.51446 10.4475 5.82769 11.0273 5.93931C11.6071 6.05092 12.2077 5.95372 12.7227 5.66491C13.2377 5.37611 13.6339 4.91435 13.841 4.36143C14.0482 3.8085 14.0529 3.20011 13.8544 2.64402C13.656 2.08793 13.2671 1.62003 12.7567 1.32322C12.2462 1.0264 11.6472 0.919827 11.0657 1.02237C10.4843 1.12491 9.95785 1.42996 9.57975 1.88346C9.20164 2.33697 8.99626 2.90966 8.99997 3.5001C9.00233 3.72369 9.03599 3.94585 9.09997 4.1601L5.44497 6.4451C5.12229 6.03889 4.68124 5.74313 4.18295 5.5988C3.68465 5.45447 3.1538 5.46873 2.66398 5.63961C2.17415 5.81048 1.74961 6.1295 1.44921 6.55245C1.14881 6.9754 0.987427 7.48133 0.987427 8.0001C0.987427 8.51888 1.14881 9.0248 1.44921 9.44775C1.74961 9.8707 2.17415 10.1897 2.66398 10.3606C3.1538 10.5315 3.68465 10.5457 4.18295 10.4014C4.68124 10.2571 5.12229 9.96131 5.44497 9.5551L9.09997 11.8401C9.03599 12.0544 9.00233 12.2765 8.99997 12.5001C8.99997 12.9946 9.14659 13.4779 9.4213 13.889C9.696 14.3002 10.0864 14.6206 10.5433 14.8098C11.0001 14.999 11.5027 15.0485 11.9877 14.9521C12.4726 14.8556 12.9181 14.6175 13.2677 14.2679C13.6174 13.9182 13.8555 13.4728 13.9519 12.9878C14.0484 12.5029 13.9989 12.0002 13.8097 11.5434C13.6204 11.0866 13.3 10.6961 12.8889 10.4214C12.4778 10.1467 11.9944 10.0001 11.5 10.0001ZM11.5 2.0001C11.7966 2.0001 12.0867 2.08808 12.3333 2.2529C12.58 2.41772 12.7723 2.65199 12.8858 2.92608C12.9993 3.20017 13.029 3.50177 12.9711 3.79274C12.9133 4.08371 12.7704 4.35098 12.5606 4.56076C12.3509 4.77054 12.0836 4.9134 11.7926 4.97128C11.5016 5.02916 11.2 4.99945 10.9259 4.88592C10.6519 4.77239 10.4176 4.58013 10.2528 4.33346C10.0879 4.08678 9.99997 3.79677 9.99997 3.5001C9.99997 3.10228 10.158 2.72075 10.4393 2.43944C10.7206 2.15814 11.1021 2.0001 11.5 2.0001ZM3.49997 9.5001C3.2033 9.5001 2.91329 9.41213 2.66661 9.24731C2.41994 9.08248 2.22768 8.84822 2.11415 8.57413C2.00062 8.30004 1.97091 7.99844 2.02879 7.70747C2.08667 7.4165 2.22953 7.14922 2.43931 6.93944C2.64909 6.72966 2.91636 6.5868 3.20733 6.52892C3.49831 6.47105 3.79991 6.50075 4.074 6.61428C4.34808 6.72781 4.58235 6.92007 4.74717 7.16675C4.912 7.41342 4.99997 7.70343 4.99997 8.0001C4.99997 8.39793 4.84193 8.77946 4.56063 9.06076C4.27933 9.34207 3.89779 9.5001 3.49997 9.5001ZM11.5 14.0001C11.2033 14.0001 10.9133 13.9121 10.6666 13.7473C10.4199 13.5825 10.2277 13.3482 10.1142 13.0741C10.0006 12.8 9.97091 12.4984 10.0288 12.2075C10.0867 11.9165 10.2295 11.6492 10.4393 11.4394C10.6491 11.2297 10.9164 11.0868 11.2073 11.0289C11.4983 10.971 11.7999 11.0008 12.074 11.1143C12.3481 11.2278 12.5824 11.4201 12.7472 11.6667C12.912 11.9134 13 12.2034 13 12.5001C13 12.8979 12.8419 13.2795 12.5606 13.5608C12.2793 13.8421 11.8978 14.0001 11.5 14.0001Z" />
11
+ </svg>
12
+ );
13
+
14
+ export const ShareIcon = styled(Icon).attrs(() => ({
15
+ 'data-component-name': 'icons/ShareIcon/ShareIcon',
16
+ }))<IconProps>`
17
+ path {
18
+ fill: ${({ color }) => getCssColorVariable(color)};
19
+ }
20
+
21
+ height: ${({ size }) => size || '16px'};
22
+ width: ${({ size }) => size || '16px'};
23
+ `;
package/src/index.ts CHANGED
@@ -245,6 +245,7 @@ export * from '@redocly/theme/icons/MarkdownFullIcon/MarkdownFullIcon';
245
245
  export * from '@redocly/theme/icons/DocumentBlankIcon/DocumentBlankIcon';
246
246
  export * from '@redocly/theme/icons/WarningSquareIcon/WarningSquareIcon';
247
247
  export * from '@redocly/theme/icons/MenuIcon/MenuIcon';
248
+ export * from '@redocly/theme/icons/NoteIcon/NoteIcon';
248
249
  export * from '@redocly/theme/icons/GlobalOutlinedIcon/GlobalOutlinedIcon';
249
250
  export * from '@redocly/theme/icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon';
250
251
  export * from '@redocly/theme/icons/FaceNeutralIcon/FaceNeutralIcon';
@@ -283,6 +284,7 @@ export * from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
283
284
  export * from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
284
285
  export * from '@redocly/theme/icons/WorkflowHierarchyIcon/WorkflowHierarchyIcon';
285
286
  export * from '@redocly/theme/icons/GenericIcon/GenericIcon';
287
+ export * from '@redocly/theme/icons/ShareIcon/ShareIcon';
286
288
  /* Layouts */
287
289
  export * from '@redocly/theme/layouts/RootLayout';
288
290
  export * from '@redocly/theme/layouts/PageLayout';
@@ -9,12 +9,7 @@ import { breakpoints } from '@redocly/theme/core/utils';
9
9
  import { PageNavigation } from '@redocly/theme/components/PageNavigation/PageNavigation';
10
10
  import { LastUpdated } from '@redocly/theme/components/LastUpdated/LastUpdated';
11
11
  import { Breadcrumbs as ThemeBreadcrumbs } from '@redocly/theme/components/Breadcrumbs/Breadcrumbs';
12
-
13
- import {
14
- CodeSnippetContext,
15
- CODE_GROUP_SNIPPET_NAME_KEY,
16
- } from '../core/contexts/CodeSnippetContext';
17
- import { useLocalState } from '../core/hooks/use-local-state';
12
+ import { CodeSnippetProvider } from '@redocly/theme/core/contexts/CodeSnippetContext';
18
13
 
19
14
  type DocumentationLayoutProps = {
20
15
  tableOfContent: React.ReactNode;
@@ -44,10 +39,8 @@ export function DocumentationLayout({
44
39
  const { editPage: themeEditPage } = config || {};
45
40
  const mergedConf = editPage ? { ...themeEditPage, ...editPage } : undefined;
46
41
 
47
- const [activeSnippetName, setActiveSnippetName] = useLocalState(CODE_GROUP_SNIPPET_NAME_KEY, '');
48
-
49
42
  return (
50
- <CodeSnippetContext.Provider value={{ activeSnippetName, setActiveSnippetName }}>
43
+ <CodeSnippetProvider>
51
44
  <LayoutWrapper data-component-name="Layout/DocumentationLayout" className={className}>
52
45
  <ContentWrapper withToc={!config?.toc?.hide}>
53
46
  <Breadcrumbs />
@@ -61,7 +54,7 @@ export function DocumentationLayout({
61
54
  </ContentWrapper>
62
55
  {tableOfContent}
63
56
  </LayoutWrapper>
64
- </CodeSnippetContext.Provider>
57
+ </CodeSnippetProvider>
65
58
  );
66
59
  }
67
60
 
@@ -5,65 +5,55 @@ import {
5
5
  type CodeBlockProps,
6
6
  } from '@redocly/theme/components/CodeBlock/CodeBlock';
7
7
  import { langToName } from '@redocly/theme/core/utils';
8
- import { useActiveCodeSnippetName } from '@redocly/theme/core/contexts';
8
+ import { useActiveCodeSnippetId } from '@redocly/theme/core/contexts';
9
+
10
+ type SnippetData = {
11
+ name: string;
12
+ languageName: string;
13
+ lang: string;
14
+ props: CodeBlockProps;
15
+ id: string;
16
+ };
9
17
 
10
18
  export function CodeGroup(props: React.PropsWithChildren<{ mode?: 'tabs' | 'dropdown' }>) {
11
19
  const mode = props.mode || 'tabs';
12
20
  const isTabsMode = mode === 'tabs';
13
21
 
14
22
  const rawSnippets = React.useMemo(
15
- () =>
16
- React.Children.toArray(props.children).map((child, idx) => {
17
- const childProps = child as React.ReactElement<CodeBlockProps>;
18
- return {
19
- name: getTabName(childProps.props, idx),
20
- languageName: langToName(childProps.props.lang || 'Default'),
21
- lang: childProps.props.lang || '',
22
- props: childProps.props,
23
- };
24
- }),
23
+ () => parseSnippetsFromChildren(props.children),
25
24
  [props.children],
26
25
  );
27
26
 
28
- const [activeSnippetName, setActiveSnippetName] = useActiveCodeSnippetName(mode);
29
-
30
- const snippets: Record<string, CodeBlockProps> = React.useMemo(
31
- () =>
32
- Object.fromEntries(
33
- rawSnippets.map((snippet) => {
34
- const getItemName = (snippet: (typeof rawSnippets)[number]) =>
35
- isTabsMode ? snippet?.name : snippet?.languageName || '';
36
-
37
- const name = getItemName(snippet);
38
-
39
- const items = rawSnippets.map((item) => ({
40
- name: getItemName(item),
41
- lang: item.lang,
42
- }));
43
- const itemsProps = {
44
- items,
45
- onChange: (name: string | string[]) => {
46
- setActiveSnippetName(name as string);
47
- },
48
- value: activeSnippetName || getItemName(rawSnippets[0]),
49
- };
50
- const snippetProps = {
51
- ...snippet.props,
52
- header: {
53
- ...snippet.props.header,
54
- title: isTabsMode ? undefined : snippet.name,
55
- },
56
- ...(isTabsMode ? { tabs: itemsProps } : { dropdown: itemsProps }),
57
- };
58
-
59
- return [name, snippetProps];
60
- }),
61
- ),
62
- [rawSnippets, activeSnippetName, isTabsMode, setActiveSnippetName],
63
- );
27
+ const groupId = React.useMemo(() => generateGroupId(rawSnippets, mode), [rawSnippets, mode]);
28
+
29
+ const [activeSnippetId, setActiveSnippetId] = useActiveCodeSnippetId(groupId, rawSnippets);
30
+
31
+ const snippets = React.useMemo(() => {
32
+ const items = createItemsFromSnippets(rawSnippets, isTabsMode);
64
33
 
65
- const firstName = Object.keys(snippets)[0];
66
- const activeSnippet = snippets[activeSnippetName] || snippets[firstName];
34
+ const itemsProps = {
35
+ items,
36
+ onChange: (id: string | string[]) => setActiveSnippetId(id as string),
37
+ value: activeSnippetId,
38
+ };
39
+
40
+ return Object.fromEntries(
41
+ rawSnippets.map((snippet: SnippetData) => {
42
+ const snippetProps = {
43
+ ...snippet.props,
44
+ header: {
45
+ ...snippet.props.header,
46
+ title: isTabsMode ? undefined : snippet.name,
47
+ },
48
+ ...(isTabsMode ? { tabs: itemsProps } : { dropdown: itemsProps }),
49
+ };
50
+
51
+ return [snippet.id, snippetProps];
52
+ }),
53
+ );
54
+ }, [rawSnippets, activeSnippetId, isTabsMode, setActiveSnippetId]);
55
+
56
+ const activeSnippet = snippets[activeSnippetId];
67
57
  if (!activeSnippet) {
68
58
  return null;
69
59
  }
@@ -71,8 +61,47 @@ export function CodeGroup(props: React.PropsWithChildren<{ mode?: 'tabs' | 'drop
71
61
  return <CodeBlockComponent {...activeSnippet} />;
72
62
  }
73
63
 
64
+ function generateContentHash(content: string): number {
65
+ let hash = 0;
66
+ for (let i = 0; i < content.length; i++) {
67
+ hash = content.charCodeAt(i) + ((hash << 5) - hash);
68
+ }
69
+ return Math.abs(hash);
70
+ }
71
+
72
+ // Generate unique group ID for CodeGroup instance
73
+ // Examples: "dropdown-8901234", "tabs-1234567"
74
+ function generateGroupId(rawSnippets: SnippetData[], mode: string): string {
75
+ const content = rawSnippets.map((s) => s.id + (s.props.source || '')).join('|') + `|${mode}`;
76
+ const hash = generateContentHash(content);
77
+
78
+ return `${mode}-${hash}`;
79
+ }
80
+
74
81
  function getTabName(props: CodeBlockProps, idx: number): string {
75
- return String(
76
- props.header?.title || props.file || langToName(props.lang || '') || 'Tab ' + String(idx + 1),
77
- );
82
+ const fallbackName = `Tab ${idx + 1}`;
83
+ return String(props.header?.title || props.file || langToName(props.lang || '') || fallbackName);
84
+ }
85
+
86
+ function parseSnippetsFromChildren(children: React.ReactNode): SnippetData[] {
87
+ return React.Children.toArray(children).map((child, idx) => {
88
+ const childProps = child as React.ReactElement<CodeBlockProps>;
89
+ const props = childProps.props;
90
+
91
+ return {
92
+ name: getTabName(props, idx),
93
+ languageName: String(langToName(props.lang || 'Default') || ''),
94
+ lang: props.lang || '',
95
+ props,
96
+ id: `${props.lang || ''}-${idx}`,
97
+ };
98
+ });
99
+ }
100
+
101
+ function createItemsFromSnippets(snippets: SnippetData[], isTabsMode: boolean) {
102
+ return snippets.map((snippet) => ({
103
+ name: isTabsMode ? snippet.name : snippet.languageName || '',
104
+ lang: snippet.lang,
105
+ id: snippet.id,
106
+ }));
78
107
  }
@@ -24,9 +24,16 @@ type TabsProps = {
24
24
  className?: string;
25
25
  size: TabsSize;
26
26
  forceReady?: boolean;
27
+ initialTab?: string;
27
28
  };
28
29
 
29
- export function Tabs({ children, className, size, forceReady = false }: TabsProps): JSX.Element {
30
+ export function Tabs({
31
+ children,
32
+ className,
33
+ size,
34
+ forceReady = false,
35
+ initialTab: propInitialTab,
36
+ }: TabsProps): JSX.Element {
30
37
  const [childrenArray, setChildrenArray] = useState<React.ReactElement<TabItemProps>[]>(
31
38
  React.Children.toArray(children) as React.ReactElement<TabItemProps>[],
32
39
  );
@@ -36,7 +43,8 @@ export function Tabs({ children, className, size, forceReady = false }: TabsProp
36
43
  }, [children]);
37
44
  const tabsContainerRef = useRef<HTMLUListElement>(null);
38
45
  const [isAnimating, setIsAnimating] = useState<boolean>(false);
39
- const initialTab = childrenArray[0]?.props.label ?? '';
46
+ const defaultInitialTab = childrenArray[0]?.props.label ?? '';
47
+ const initialTab = propInitialTab ?? defaultInitialTab;
40
48
  const {
41
49
  activeTab,
42
50
  setTabRef,