@redocly/theme 0.58.0-next.8 → 0.58.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 (56) hide show
  1. package/lib/components/Catalog/CatalogEntity/CatalogEntity.d.ts +5 -1
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +4 -4
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +3 -3
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.d.ts +5 -1
  5. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +9 -7
  6. package/lib/components/CatalogClassic/CatalogClassic.js +9 -2
  7. package/lib/components/CodeBlock/CodeBlock.d.ts +5 -12
  8. package/lib/components/CodeBlock/CodeBlockControls.d.ts +3 -3
  9. package/lib/components/CodeBlock/CodeBlockControls.js +1 -1
  10. package/lib/components/CodeBlock/CodeBlockDropdown.d.ts +2 -2
  11. package/lib/components/CodeBlock/CodeBlockDropdown.js +4 -13
  12. package/lib/components/CodeBlock/CodeBlockTabs.d.ts +2 -2
  13. package/lib/components/CodeBlock/CodeBlockTabs.js +4 -3
  14. package/lib/components/JsonViewer/JsonViewer.d.ts +1 -1
  15. package/lib/components/JsonViewer/JsonViewer.js +9 -10
  16. package/lib/components/PageActions/PageActions.d.ts +4 -1
  17. package/lib/components/PageActions/PageActions.js +2 -2
  18. package/lib/core/constants/catalog.js +4 -0
  19. package/lib/core/contexts/CodeSnippetContext.d.ts +14 -6
  20. package/lib/core/contexts/CodeSnippetContext.js +57 -14
  21. package/lib/core/hooks/use-codeblock-tabs-controls.d.ts +2 -2
  22. package/lib/core/hooks/use-local-state.js +22 -18
  23. package/lib/core/hooks/use-page-actions.d.ts +2 -1
  24. package/lib/core/hooks/use-page-actions.js +48 -6
  25. package/lib/core/openapi/index.d.ts +1 -0
  26. package/lib/core/openapi/index.js +3 -1
  27. package/lib/core/types/l10n.d.ts +1 -1
  28. package/lib/core/types/open-api-server.d.ts +1 -0
  29. package/lib/icons/CursorIcon/CursorIcon.d.ts +9 -0
  30. package/lib/icons/CursorIcon/CursorIcon.js +22 -0
  31. package/lib/layouts/DocumentationLayout.js +1 -3
  32. package/lib/markdoc/components/CodeGroup/CodeGroup.js +49 -27
  33. package/lib/markdoc/components/Tabs/TabList.js +2 -0
  34. package/package.json +4 -4
  35. package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +15 -2
  36. package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +3 -3
  37. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +27 -18
  38. package/src/components/CatalogClassic/CatalogClassic.tsx +26 -10
  39. package/src/components/CodeBlock/CodeBlock.tsx +5 -11
  40. package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
  41. package/src/components/CodeBlock/CodeBlockDropdown.tsx +11 -20
  42. package/src/components/CodeBlock/CodeBlockTabs.tsx +8 -8
  43. package/src/components/JsonViewer/JsonViewer.tsx +16 -9
  44. package/src/components/PageActions/PageActions.tsx +6 -4
  45. package/src/core/constants/catalog.ts +4 -0
  46. package/src/core/contexts/CodeSnippetContext.tsx +54 -18
  47. package/src/core/hooks/use-codeblock-tabs-controls.ts +2 -2
  48. package/src/core/hooks/use-local-state.ts +28 -19
  49. package/src/core/hooks/use-page-actions.ts +63 -6
  50. package/src/core/openapi/index.ts +1 -0
  51. package/src/core/types/l10n.ts +13 -0
  52. package/src/core/types/open-api-server.ts +1 -0
  53. package/src/icons/CursorIcon/CursorIcon.tsx +35 -0
  54. package/src/layouts/DocumentationLayout.tsx +3 -10
  55. package/src/markdoc/components/CodeGroup/CodeGroup.tsx +81 -52
  56. package/src/markdoc/components/Tabs/TabList.tsx +1 -0
@@ -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
  }
@@ -104,6 +104,7 @@ export function TabList({
104
104
  key={`more-${tabId}`}
105
105
  active={activeTab === label}
106
106
  onAction={() => {
107
+ childrenArray[index].props.onClick?.();
107
108
  onTabClick(index);
108
109
  }}
109
110
  disabled={childrenArray[index].props.disable}