@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
@@ -1,24 +1,23 @@
1
1
  import React, { type JSX } from 'react';
2
- import styled from 'styled-components';
3
2
 
4
- import type { CodeBlockDropdownItems } from '@redocly/theme/components/CodeBlock/CodeBlock';
3
+ import type { CodeBlockItems } from '@redocly/theme/components/CodeBlock/CodeBlock';
5
4
 
6
5
  import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
7
6
  import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
8
7
  import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
9
8
  import { Button } from '@redocly/theme/components/Button/Button';
10
- import { ChevronSortIcon } from '@redocly/theme/icons/ChevronSortIcon/ChevronSortIcon';
11
9
  import { NoneIcon } from '@redocly/theme/icons/NoneIcon/NoneIcon';
12
10
  import { getFileIconByLanguage } from '@redocly/theme/core/utils';
13
11
 
14
- export function CodeBlockDropdown({ items, onChange, value }: CodeBlockDropdownItems): JSX.Element {
15
- const activeItem = items.find((item) => item.name === value) || items[0];
12
+ export function CodeBlockDropdown({ items, onChange, value }: CodeBlockItems): JSX.Element {
13
+ const activeItem = items.find((item) => item.id === value) || items[0];
16
14
  const icon = activeItem?.lang ? getFileIconByLanguage(activeItem?.lang) : null;
17
15
  return (
18
- <StyledDropdown
16
+ <Dropdown
17
+ withArrow
19
18
  alignment="end"
20
19
  trigger={
21
- <Button icon={<ChevronSortIcon />} iconPosition="right" variant="text" size="small">
20
+ <Button iconPosition="right" variant="ghost" size="small">
22
21
  {icon}
23
22
  {activeItem.name}
24
23
  </Button>
@@ -27,11 +26,12 @@ export function CodeBlockDropdown({ items, onChange, value }: CodeBlockDropdownI
27
26
  <DropdownMenu>
28
27
  {items.map((item) => {
29
28
  const icon = getFileIconByLanguage(item.lang || '');
29
+ const isActive = item.id === value;
30
30
  return (
31
31
  <DropdownMenuItem
32
- key={item.lang}
33
- onAction={() => onChange(item.name)}
34
- active={item.name === activeItem.name}
32
+ key={item.id}
33
+ onAction={() => onChange(item.id)}
34
+ active={isActive}
35
35
  prefix={item.lang ? icon : <NoneIcon size="var(--icon-size)" />}
36
36
  >
37
37
  {item.name}
@@ -39,15 +39,6 @@ export function CodeBlockDropdown({ items, onChange, value }: CodeBlockDropdownI
39
39
  );
40
40
  })}
41
41
  </DropdownMenu>
42
- </StyledDropdown>
42
+ </Dropdown>
43
43
  );
44
44
  }
45
-
46
- const StyledDropdown = styled(Dropdown)`
47
- margin-left: auto;
48
- --icon-size: 18px;
49
- --button-color: var(--text-color-secondary);
50
- button.button-size-small {
51
- --button-icon-size: 18px;
52
- }
53
- `;
@@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
- import type { CodeBlockTabItems } from '@redocly/theme/components/CodeBlock/CodeBlock';
5
+ import type { CodeBlockItems } from '@redocly/theme/components/CodeBlock/CodeBlock';
6
6
 
7
7
  import { useCodeBlockTabsControls } from '@redocly/theme/core/hooks';
8
8
  import { Button } from '@redocly/theme/components/Button/Button';
@@ -11,7 +11,7 @@ import { ChevronRightIcon } from '@redocly/theme/icons/ChevronRightIcon/ChevronR
11
11
  import { getFileIconByExt, getFileIconByLanguage } from '@redocly/theme/core/utils/get-file-icon';
12
12
 
13
13
  export type CodeBlockTabsProps = {
14
- tabs: CodeBlockTabItems;
14
+ tabs: CodeBlockItems;
15
15
  };
16
16
 
17
17
  export function CodeBlockTabs({ tabs }: CodeBlockTabsProps): JSX.Element {
@@ -24,7 +24,7 @@ export function CodeBlockTabs({ tabs }: CodeBlockTabsProps): JSX.Element {
24
24
  });
25
25
 
26
26
  useEffect(() => {
27
- const activeTab = tabRefs.current.find((tab) => tab?.dataset.name === tabs.value);
27
+ const activeTab = tabRefs.current.find((tab) => tab?.dataset.id === tabs.value);
28
28
 
29
29
  if (activeTab) {
30
30
  activeTab.scrollIntoView({ block: 'nearest', inline: 'center' });
@@ -35,23 +35,23 @@ export function CodeBlockTabs({ tabs }: CodeBlockTabsProps): JSX.Element {
35
35
  <CodeBlockTabsWrapper ref={containerRef} data-component-name="CodeBlock/CodeBlockTabs">
36
36
  <ShadowWrapper>
37
37
  <Tabs>
38
- {tabs.items.map(({ name, lang }, i) => {
38
+ {tabs.items.map((item, i) => {
39
+ const { name, lang, id } = item;
39
40
  const ext = name.match(/\.([^.]+)$/)?.[1];
40
41
  const fileIcon = lang
41
42
  ? getFileIconByLanguage(lang)
42
43
  : ext
43
44
  ? getFileIconByExt(ext)
44
45
  : null;
45
-
46
46
  return (
47
47
  <Tab
48
48
  ref={(el: HTMLButtonElement | null) => {
49
49
  tabRefs.current[i] = el as HTMLButtonElement;
50
50
  }}
51
51
  data-name={name}
52
- active={name === tabs.value}
53
- key={name + i}
54
- onClick={() => tabs.onChange(name)}
52
+ active={id === tabs.value}
53
+ key={id}
54
+ onClick={() => tabs.onChange(id)}
55
55
  >
56
56
  {fileIcon}
57
57
  {name}
@@ -306,13 +306,16 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
306
306
  aria-selected="true"
307
307
  >
308
308
  <AiStarsIcon
309
+ style={{ flexShrink: 0 }}
309
310
  color="var(--search-ai-icon-color)"
310
311
  size="36px"
311
312
  background="var(--search-ai-icon-bg-color)"
312
313
  margin="0 var(--spacing-md) 0 0"
313
314
  borderRadius="var(--border-radius-lg)"
314
315
  />
315
- <Typography fontWeight="var(--font-weight-semibold)">{query}</Typography>
316
+ <QueryWrapper>
317
+ <Typography fontWeight="var(--font-weight-semibold)">{query}</Typography>
318
+ </QueryWrapper>
316
319
  <Typography>- {translate('search.ai.label', 'Ask AI assistant')}</Typography>
317
320
  <ReturnKeyIcon color="var(--search-item-text-color)" />
318
321
  </SearchWithAI>
@@ -484,10 +487,10 @@ const SearchDialogWrapper = styled.div`
484
487
  border-radius: 0;
485
488
 
486
489
  @media screen and (max-width: ${breakpoints.small}) {
487
- min-height: -webkit-fill-available !important;
488
- min-height: 100dvh !important;
489
- height: auto !important;
490
- width: 100vw !important;
490
+ min-height: -webkit-fill-available;
491
+ min-height: 100dvh;
492
+ height: 100dvh;
493
+ width: 100vw;
491
494
  }
492
495
 
493
496
  @media screen and (min-width: ${breakpoints.small}) {
@@ -539,6 +542,8 @@ const SearchDialogBodyMainView = styled.div`
539
542
 
540
543
  const SearchDialogBodyFilterView = styled.div`
541
544
  overflow: scroll;
545
+ max-width: var(--search-filter-width);
546
+ width: 100%;
542
547
  `;
543
548
 
544
549
  const SearchDialogFooter = styled.footer`
@@ -673,3 +678,7 @@ const SearchWithAI = styled.div`
673
678
  margin-right: var(--spacing-xs);
674
679
  }
675
680
  `;
681
+
682
+ const QueryWrapper = styled.div`
683
+ word-break: break-word;
684
+ `;
@@ -66,7 +66,8 @@ export function SearchFilter({
66
66
  }
67
67
 
68
68
  const SearchFilterWrapper = styled.div`
69
- width: var(--search-filter-width);
69
+ width: 100%;
70
+ max-width: var(--search-filter-width);
70
71
  display: flex;
71
72
  flex-direction: column;
72
73
  padding: var(--search-filter-padding);
@@ -98,14 +98,12 @@ export function TooltipComponent({
98
98
  }, [isOpened, placement, updateTooltipPosition]);
99
99
 
100
100
  useEffect(() => {
101
- if (isControlled && !disabled) {
102
- if (isOpen) {
103
- handleOpen();
104
- } else {
105
- handleClose();
106
- }
101
+ if (isOpen && !disabled) {
102
+ handleOpen();
103
+ } else {
104
+ handleClose();
107
105
  }
108
- }, [isOpen, isControlled, handleOpen, handleClose, disabled]);
106
+ }, [isOpen, handleOpen, handleClose, disabled]);
109
107
 
110
108
  const controllers = !isControlled &&
111
109
  !disabled && {
@@ -268,7 +266,7 @@ const TooltipBody = styled.span<
268
266
  display: inline-block;
269
267
 
270
268
  padding: var(--tooltip-padding);
271
- max-width: var(--tooltip-max-width);
269
+ max-width: ${({ width }) => width || 'var(--tooltip-max-width)'};
272
270
  white-space: normal;
273
271
  word-break: normal;
274
272
  overflow-wrap: break-word;
@@ -26,7 +26,7 @@ export function TooltipWrapper({
26
26
  showOnHover = true,
27
27
  disabled = false,
28
28
  }: TooltipWrapperProps): JSX.Element {
29
- const tooltip = useControl();
29
+ const tooltip = useControl(false);
30
30
 
31
31
  const handleMouseEnter = (): void => {
32
32
  if (showOnHover && !disabled) {
@@ -2,33 +2,15 @@ import type { EntityRelationType } from '../types/catalog';
2
2
 
3
3
  export const CATALOG_TAG_MAX_LENGTH = 15;
4
4
 
5
- export const ENTITY_RELATION_TYPES = [
6
- 'partOf',
7
- 'hasParts',
8
- 'creates',
9
- 'createdBy',
10
- 'owns',
11
- 'ownedBy',
12
- 'implements',
13
- 'implementedBy',
14
- 'dependsOn',
15
- 'dependencyOf',
16
- 'uses',
17
- 'usedBy',
18
- 'produces',
19
- 'consumes',
20
- 'linksTo',
21
- 'supersedes',
22
- 'supersededBy',
23
- 'compatibleWith',
24
- 'extends',
25
- 'extendedBy',
26
- 'relatesTo',
27
- 'hasMember',
28
- 'memberOf',
29
- 'triggers',
30
- 'triggeredBy',
31
- ] as const;
5
+ export const PREDEFINED_ENTITY_TYPES = [
6
+ 'service',
7
+ 'domain',
8
+ 'team',
9
+ 'user',
10
+ 'api-description',
11
+ 'data-schema',
12
+ 'api-operation',
13
+ ];
32
14
 
33
15
  export const reverseRelationMap: Record<EntityRelationType, EntityRelationType> = {
34
16
  partOf: 'hasParts',
@@ -56,6 +38,8 @@ export const reverseRelationMap: Record<EntityRelationType, EntityRelationType>
56
38
  memberOf: 'hasMember',
57
39
  triggers: 'triggeredBy',
58
40
  triggeredBy: 'triggers',
41
+ returns: 'returnedBy',
42
+ returnedBy: 'returns',
59
43
  } as const;
60
44
 
61
45
  export const relationTypeMap: Record<EntityRelationType, string> = {
@@ -84,6 +68,8 @@ export const relationTypeMap: Record<EntityRelationType, string> = {
84
68
  memberOf: 'Member of',
85
69
  triggers: 'Triggers',
86
70
  triggeredBy: 'Triggered by',
71
+ returns: 'Returns',
72
+ returnedBy: 'Returned by',
87
73
  };
88
74
 
89
75
  export enum GraphHandleType {
@@ -1,31 +1,67 @@
1
- import { createContext, useContext, useState } from 'react';
1
+ import React, { createContext, useContext, useCallback, useMemo } from 'react';
2
2
 
3
- export type CodeSnippetContextType = {
4
- activeSnippetName: string;
5
- setActiveSnippetName: (name: string) => void;
6
- };
3
+ import { useLocalState } from '../hooks/use-local-state';
7
4
 
8
5
  export const CODE_GROUP_SNIPPET_NAME_KEY = 'redocly:codeGroupSnippetName';
9
6
 
7
+ type CodeSnippetContextType = {
8
+ activeSnippets: Record<string, string>;
9
+ setActiveSnippet: (groupId: string, snippetId: string) => void;
10
+ };
11
+
10
12
  export const CodeSnippetContext = createContext<CodeSnippetContextType | null>(null);
11
13
 
12
- export function useActiveCodeSnippetName(
13
- mode: 'tabs' | 'dropdown',
14
- ): [string, (name: string) => void] {
14
+ export function CodeSnippetProvider({ children }: { children: React.ReactNode }) {
15
+ const [activeSnippets, setActiveSnippets] = useLocalState<Record<string, string>>(
16
+ CODE_GROUP_SNIPPET_NAME_KEY,
17
+ {},
18
+ );
19
+
20
+ const setActiveSnippet = useCallback(
21
+ (groupId: string, snippetId: string) => {
22
+ setActiveSnippets({ ...activeSnippets, [groupId]: snippetId });
23
+ },
24
+ [activeSnippets, setActiveSnippets],
25
+ );
26
+
27
+ const contextValue = { activeSnippets, setActiveSnippet };
28
+
29
+ return <CodeSnippetContext.Provider value={contextValue}>{children}</CodeSnippetContext.Provider>;
30
+ }
31
+
32
+ export const useCodeSnippetContext = (): CodeSnippetContextType => {
15
33
  const context = useContext(CodeSnippetContext);
34
+
16
35
  if (!context) {
17
36
  throw new Error('useCodeSnippetContext must be used within a CodeSnippetContext');
18
37
  }
19
38
 
20
- const [activeSnippetName, setActiveSnippetName] = useState(
21
- mode === 'tabs' ? '' : context.activeSnippetName,
39
+ return context;
40
+ };
41
+
42
+ export const useActiveCodeSnippetId = (
43
+ groupId?: string,
44
+ availableSnippets?: { id: string }[],
45
+ ): [string, (id: string) => void] => {
46
+ const { activeSnippets, setActiveSnippet } = useCodeSnippetContext();
47
+
48
+ const storedSnippetId = groupId ? activeSnippets[groupId] || '' : '';
49
+
50
+ const activeId = useMemo(() => {
51
+ if (!availableSnippets?.length) return storedSnippetId;
52
+
53
+ const found = storedSnippetId && availableSnippets.find((s) => s.id === storedSnippetId);
54
+ return found ? storedSnippetId : availableSnippets[0]?.id || '';
55
+ }, [storedSnippetId, availableSnippets]);
56
+
57
+ const setActiveSnippetId = useCallback(
58
+ (id: string) => {
59
+ if (groupId) {
60
+ setActiveSnippet(groupId, id);
61
+ }
62
+ },
63
+ [groupId, setActiveSnippet],
22
64
  );
23
65
 
24
- if (mode === 'tabs') {
25
- // use non-synced state for tabs mode
26
- return [activeSnippetName, setActiveSnippetName];
27
- } else {
28
- // use global synced state for dropdown mode
29
- return [context.activeSnippetName, context.setActiveSnippetName];
30
- }
31
- }
66
+ return [activeId, setActiveSnippetId];
67
+ };
@@ -21,7 +21,7 @@ export function useCatalogTableViewRow({
21
21
  }
22
22
 
23
23
  return Object.values(entitiesCatalogConfig.catalogs ?? {}).find((catalog) => {
24
- return catalog.includes?.some((include) => include.type === entityType);
24
+ return catalog?.includes?.some((include) => include.type === entityType);
25
25
  });
26
26
  };
27
27
 
@@ -25,6 +25,12 @@ export function useActiveSectionId(
25
25
  setItemId('');
26
26
  return;
27
27
  }
28
+
29
+ if (window.scrollY <= 0) {
30
+ setItemId(sections[0].getAttribute('data-section-id') || '');
31
+ return;
32
+ }
33
+
28
34
  for (let i = 0; i < sections.length; i++) {
29
35
  const section = sections[i];
30
36
  const rect = section.getBoundingClientRect();
@@ -1,9 +1,9 @@
1
1
  import { useEffect, useState, useCallback, useMemo } from 'react';
2
2
 
3
- import type { CodeBlockTabItems } from '../../components/CodeBlock/CodeBlock';
3
+ import type { CodeBlockItems } from '../../components/CodeBlock/CodeBlock';
4
4
 
5
5
  type CodeBlockTabsProps = {
6
- tabs: CodeBlockTabItems;
6
+ tabs: CodeBlockItems;
7
7
  containerRef: React.RefObject<HTMLDivElement | null>;
8
8
  tabRefs: React.RefObject<HTMLButtonElement[]>;
9
9
  };
@@ -1,4 +1,6 @@
1
- import { useState, useCallback } from 'react';
1
+ import { useState, useCallback, useRef } from 'react';
2
+
3
+ const DEFAULT_CONTROL_OPEN_DELAY = 300;
2
4
 
3
5
  export type UseControlReturnType = {
4
6
  isOpened: boolean;
@@ -8,9 +10,25 @@ export type UseControlReturnType = {
8
10
 
9
11
  export const useControl = (initialVal = false): UseControlReturnType => {
10
12
  const [isOpened, setIsOpened] = useState<boolean>(initialVal);
13
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
14
+
15
+ const clearOpenTimer = useCallback(() => {
16
+ if (timeoutRef.current) {
17
+ clearTimeout(timeoutRef.current);
18
+ }
19
+ }, []);
20
+
21
+ const handleOpen = useCallback(() => {
22
+ clearOpenTimer();
23
+ timeoutRef.current = setTimeout(() => {
24
+ setIsOpened(true);
25
+ }, DEFAULT_CONTROL_OPEN_DELAY);
26
+ }, [clearOpenTimer]);
11
27
 
12
- const handleOpen = useCallback(() => setIsOpened(true), []);
13
- const handleClose = useCallback(() => setIsOpened(false), []);
28
+ const handleClose = useCallback(() => {
29
+ clearOpenTimer();
30
+ setIsOpened(false);
31
+ }, [clearOpenTimer]);
14
32
 
15
33
  return {
16
34
  isOpened,
@@ -1,30 +1,39 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ import { isBrowser } from '../utils/js-utils';
4
+
5
+ function getStoredValue<T>(key: string, fallback: T): T {
6
+ if (!isBrowser()) return fallback;
2
7
 
3
- function getInitialValue<T>(key: string, initialValue: T): T {
4
- if (typeof window === 'undefined') {
5
- return initialValue;
6
- }
7
8
  try {
8
9
  const savedValue = localStorage.getItem(key);
9
- if (savedValue) {
10
- return JSON.parse(savedValue) as T;
11
- }
12
- } catch (error) {
13
- console.error(`Error reading from localStorage for key "${key}":`, error);
10
+ return savedValue ? (JSON.parse(savedValue) as T) : fallback;
11
+ } catch {
12
+ return fallback;
14
13
  }
15
- return initialValue;
16
14
  }
17
15
 
18
16
  export function useLocalState<T>(key: string, initialValue: T): [T, (value: T) => void] {
19
- const [value, setValue] = useState<T>(() => getInitialValue(key, initialValue));
17
+ const [value, setValue] = useState<T>(initialValue);
20
18
 
19
+ // Load stored value from localStorage after component mounts
20
+ // This ensures SSR compatibility: server and client both start with initialValue,
21
+ // then client loads the actual stored value without hydration mismatch
21
22
  useEffect(() => {
22
- try {
23
- localStorage.setItem(key, JSON.stringify(value));
24
- } catch (error) {
25
- console.error(`Error writing to localStorage for key "${key}":`, error);
26
- }
27
- }, [key, value]);
23
+ if (!isBrowser()) return;
24
+ setValue(getStoredValue(key, initialValue));
25
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
+ }, [key]);
27
+
28
+ const handleSetValue = useCallback(
29
+ (newValue: T) => {
30
+ setValue(newValue);
31
+ if (isBrowser()) {
32
+ localStorage.setItem(key, JSON.stringify(newValue));
33
+ }
34
+ },
35
+ [key],
36
+ );
28
37
 
29
- return [value, setValue] as const;
38
+ return [value, handleSetValue] as const;
30
39
  }
@@ -58,4 +58,9 @@ export const useTelemetryFallback = () => ({
58
58
  sendAsyncapiDocsMessageClickedMessage: () => {},
59
59
  sendAsyncapiDocsServerModalOpenedMessage: () => {},
60
60
  sendAsyncapiDocsDownloadDefinitionClickedMessage: () => {},
61
+ sendGraphqlDocsViewedMessage: () => {},
62
+ sendGraphqlDocsPerformanceMetricsMessage: () => {},
63
+ sendGraphqlDocsReferencedInLinkClickedMessage: () => {},
64
+ sendGraphqlDocsRequiredScopesModalOpenedMessage: () => {},
65
+ sendGraphqlDocsDownloadDefinitionClickedMessage: () => {},
61
66
  });
@@ -4,6 +4,10 @@
4
4
  // This selective import approach will help with tree shaking and reduce
5
5
  // the overall bundle size by avoiding importing the entire theme package
6
6
  // when only specific functionality is needed.
7
+ export type { UserClaims } from '../types/user-claims';
8
+ export type { OperationParameter, ParameterHighlight } from '../types/search';
9
+ export type { TFunction, TOptions } from '../types/l10n';
10
+ export type { SelectOption, SelectProps } from '../types/select';
7
11
  export { IS_BROWSER } from '../utils/dom';
8
12
  export {
9
13
  addLeadingSlash,
@@ -18,15 +22,15 @@ export { useMount } from '../hooks/use-mount';
18
22
  export { GlobalStyle } from '../styles/global';
19
23
  export { breakpoints } from '../utils/media-css';
20
24
  export { isPrimitive } from '../utils/args-typecheck';
21
- export type { UserClaims } from '../types/user-claims';
25
+ export { ClipboardService } from '../utils/clipboard-service';
26
+ export { getUserAgent } from '../utils/get-user-agent';
22
27
  export { useFocusTrap } from '../hooks/use-focus-trap';
23
- export type { TFunction, TOptions } from '../types/l10n';
24
28
  export { useThemeHooks } from '../hooks/use-theme-hooks';
25
29
  export { useOutsideClick } from '../hooks/use-outside-click';
26
- export type { SelectOption, SelectProps } from '../types/select';
27
30
  export { useActiveSectionId } from '../hooks/use-active-section-id';
28
31
  export { useModalScrollLock } from '../hooks/use-modal-scroll-lock';
32
+ export { useSearchDialog } from '../hooks/search/use-search-dialog';
33
+ export { useDialogHotKeys } from '../hooks/use-dialog-hotkeys';
29
34
  export { SecurityVariablesEnvSuffix } from '../constants/environments';
30
35
  export { isUndefined, isString, isNotNull, isObject } from '../utils/type-guards';
31
36
  export { ThemeDataContext, type ThemeDataTransferObject } from '../contexts/ThemeDataContext';
32
- export { ENTITY_RELATION_TYPES } from '../constants/catalog';
@@ -780,6 +780,25 @@ const apiReferenceDocs = css`
780
780
  --fab-icon-color: var(--navbar-text-color); // @presenter Color
781
781
 
782
782
  // @tokens End
783
+
784
+ /**
785
+ * @tokens OpenAPI Schema Catalog Link
786
+ */
787
+
788
+ --schema-catalog-link-margin-bottom: var(--spacing-lg);
789
+ --schema-catalog-link-padding: 2px;
790
+ --schema-catalog-link-border-radius: var(--border-radius-md);
791
+ --schema-catalog-link-background-color: var(--layer-color);
792
+ --schema-catalog-link-color: var(--color-purple-7);
793
+
794
+ --schema-catalog-link-share-icon-color: var(--color-purple-7);
795
+ --schema-catalog-link-share-icon-background-color: var(--color-purple-1);
796
+ --schema-catalog-link-share-icon-border-radius: var(--border-radius-md);
797
+ --schema-catalog-link-share-icon-wrapper-size: var(--spacing-xl);
798
+
799
+ --schema-catalog-link-text-color: var(--text-color-primary);
800
+
801
+ // @tokens End
783
802
  `;
784
803
 
785
804
  const badges = css`
@@ -1,10 +1,9 @@
1
1
  import { InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query';
2
2
  import { CatalogEntityConfig, LayoutVariant } from '@redocly/config';
3
+ import { ENTITY_RELATION_TYPES } from '@redocly/config';
3
4
 
4
5
  import type { CatalogFilterConfig } from '@redocly/theme/config';
5
6
 
6
- import { ENTITY_RELATION_TYPES } from '../constants/catalog';
7
-
8
7
  export type SortOption = 'title' | '-title' | 'type' | '-type';
9
8
 
10
9
  export type UseCatalogResponse = {
@@ -17,6 +17,7 @@ import type {
17
17
  FilteredCatalog,
18
18
  UseCatalogSortResponse,
19
19
  UseCatalogSearchResponse,
20
+ CatalogViewMode,
20
21
  } from './catalog';
21
22
  import type { UserMenuData } from './user-menu';
22
23
  import type { ItemState } from './sidebar';
@@ -139,7 +140,11 @@ export type ThemeHooks = {
139
140
  nextPage?: ResolvedNavItemWithLink;
140
141
  }
141
142
  | undefined;
142
- useCatalog: (config?: CatalogEntityConfig, entitiesCounterInitial?: number) => UseCatalogResponse;
143
+ useCatalog: (
144
+ config?: CatalogEntityConfig,
145
+ entitiesCounterInitial?: number,
146
+ initialViewMode?: CatalogViewMode,
147
+ ) => UseCatalogResponse;
143
148
  useCatalogSort: () => UseCatalogSortResponse;
144
149
  useCatalogSearch: () => UseCatalogSearchResponse;
145
150
  useFetchCatalogEntities: (
@@ -16,3 +16,4 @@ export * from './open-api-server';
16
16
  export * from './marker';
17
17
  export * from './code-walkthrough';
18
18
  export * from './page-actions';
19
+ export * from './open-api-info';
@@ -280,6 +280,9 @@ export type TranslationKey =
280
280
  | 'openapi.requiredScopes'
281
281
  | 'openapi.unsupportedLanguage'
282
282
  | 'openapi.failedToGenerateCodeSample'
283
+ | 'openapi.schemaCatalogLink.title'
284
+ | 'openapi.schemaCatalogLink.copyButtonTooltip'
285
+ | 'openapi.schemaCatalogLink.copiedTooltip'
283
286
  | 'asyncapi.download.description.title'
284
287
  | 'asyncapi.info.title'
285
288
  | 'graphql.queries'
@@ -0,0 +1,34 @@
1
+ export type OpenAPIInfo = {
2
+ title: string;
3
+ version: string;
4
+ description?: string;
5
+ summary?: string;
6
+ termsOfService?: string;
7
+ contact?: {
8
+ name?: string;
9
+ url?: string;
10
+ email?: string;
11
+ };
12
+ license?: {
13
+ name: string;
14
+ url?: string;
15
+ identifier?: string;
16
+ };
17
+ externalDocs?: {
18
+ description?: string;
19
+ url: string;
20
+ };
21
+ 'x-logo'?: {
22
+ url?: string;
23
+ backgroundColor?: string;
24
+ altText?: string;
25
+ href?: string;
26
+ };
27
+ 'x-metadata'?: {
28
+ apiId?: string;
29
+ [key: string]: unknown;
30
+ };
31
+ 'x-seo'?: {
32
+ title?: string;
33
+ };
34
+ };
@@ -1,6 +1,7 @@
1
1
  export type OpenAPIServer = {
2
2
  url: string;
3
3
  description?: string;
4
+ name?: string;
4
5
  variables?: Record<
5
6
  string,
6
7
  {