@eventcatalog/core 2.62.1 → 2.64.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 (64) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-4BYDUGYI.js → chunk-6AMZOBWI.js} +1 -1
  6. package/dist/{chunk-GZ2SVHEA.js → chunk-CWGFHLMX.js} +1 -1
  7. package/dist/{chunk-IWFL6VRS.js → chunk-PLMTJHGH.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +1 -1
  11. package/dist/eventcatalog.config.d.cts +34 -0
  12. package/dist/eventcatalog.config.d.ts +34 -0
  13. package/dist/eventcatalog.js +3 -3
  14. package/eventcatalog/astro.config.mjs +2 -1
  15. package/eventcatalog/public/icons/avro.svg +21 -0
  16. package/eventcatalog/public/icons/json-schema.svg +6 -0
  17. package/eventcatalog/public/icons/proto.svg +10 -0
  18. package/eventcatalog/src/components/Grids/utils.tsx +5 -3
  19. package/eventcatalog/src/components/MDX/RemoteFile.astro +5 -11
  20. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro +41 -6
  21. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +139 -0
  22. package/eventcatalog/src/components/SchemaExplorer/AvroSchemaViewer.tsx +423 -0
  23. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +102 -0
  24. package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.tsx +740 -0
  25. package/eventcatalog/src/components/SchemaExplorer/OwnersSection.tsx +56 -0
  26. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +33 -0
  27. package/eventcatalog/src/components/SchemaExplorer/ProducersConsumersSection.tsx +91 -0
  28. package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +93 -0
  29. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +130 -0
  30. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +181 -0
  31. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +232 -0
  32. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +415 -0
  33. package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +174 -0
  34. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +73 -0
  35. package/eventcatalog/src/components/SchemaExplorer/SchemaViewerModal.tsx +77 -0
  36. package/eventcatalog/src/components/SchemaExplorer/VersionHistoryModal.tsx +72 -0
  37. package/eventcatalog/src/components/SchemaExplorer/types.ts +45 -0
  38. package/eventcatalog/src/components/SchemaExplorer/utils.ts +81 -0
  39. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +33 -2
  40. package/eventcatalog/src/components/Tables/Table.tsx +10 -2
  41. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +10 -8
  42. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +8 -6
  43. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +9 -7
  44. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +11 -9
  45. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +9 -7
  46. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +4 -2
  47. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +12 -8
  48. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +14 -9
  49. package/eventcatalog/src/components/Tables/columns/index.tsx +9 -8
  50. package/eventcatalog/src/content.config.ts +3 -2
  51. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +1 -0
  52. package/eventcatalog/src/layouts/DirectoryLayout.astro +21 -22
  53. package/eventcatalog/src/layouts/DiscoverLayout.astro +26 -12
  54. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -0
  55. package/eventcatalog/src/pages/api/schemas/[collection]/[id]/[version]/index.ts +45 -0
  56. package/eventcatalog/src/pages/api/schemas/services/[id]/[version]/[specification]/index.ts +51 -0
  57. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -0
  58. package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +86 -0
  59. package/eventcatalog/src/pages/schemas/index.astro +175 -0
  60. package/eventcatalog/src/types/index.ts +9 -0
  61. package/eventcatalog/src/utils/files.ts +9 -0
  62. package/package.json +1 -1
  63. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaProperty.astro +0 -204
  64. package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewer.astro +0 -705
@@ -0,0 +1,56 @@
1
+ import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
2
+ import { UserIcon, UserGroupIcon } from '@heroicons/react/20/solid';
3
+ import type { SchemaItem, Owner } from './types';
4
+
5
+ interface OwnersSectionProps {
6
+ message: SchemaItem;
7
+ isExpanded: boolean;
8
+ onToggle: () => void;
9
+ }
10
+
11
+ export default function OwnersSection({ message, isExpanded, onToggle }: OwnersSectionProps) {
12
+ const owners = message.data.owners || [];
13
+
14
+ if (owners.length === 0) return null;
15
+
16
+ return (
17
+ <div className="flex-shrink-0 border-b border-gray-200">
18
+ <button
19
+ onClick={onToggle}
20
+ className="w-full flex items-center justify-between px-4 py-1.5 text-left hover:bg-gray-50 transition-colors"
21
+ >
22
+ <div className="flex items-center gap-2">
23
+ <UserIcon className="h-4 w-4 text-gray-600" />
24
+ <span className="text-xs font-semibold text-gray-900">Owners</span>
25
+ <span className="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">
26
+ {owners.length}
27
+ </span>
28
+ </div>
29
+ {isExpanded ? <ChevronUpIcon className="h-4 w-4 text-gray-600" /> : <ChevronDownIcon className="h-4 w-4 text-gray-600" />}
30
+ </button>
31
+
32
+ {isExpanded && (
33
+ <div className="px-4 pb-2 bg-gray-50">
34
+ <div className="flex flex-wrap gap-2">
35
+ {owners.map((owner: Owner, idx: number) => {
36
+ const Icon = owner.type === 'users' ? UserIcon : UserGroupIcon;
37
+ return (
38
+ <a
39
+ key={`${owner.id}-${idx}`}
40
+ href={owner.href}
41
+ className="inline-flex items-center gap-1.5 pl-1 pr-3 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-200 rounded-full hover:border-gray-300 hover:shadow-sm transition-all"
42
+ title={owner.name}
43
+ >
44
+ <div className="flex items-center justify-center w-5 h-5 bg-gradient-to-b from-purple-500 to-purple-600 rounded-full">
45
+ <Icon className="h-3 w-3 text-white" />
46
+ </div>
47
+ <span className="font-medium">{owner.name}</span>
48
+ </a>
49
+ );
50
+ })}
51
+ </div>
52
+ </div>
53
+ )}
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,33 @@
1
+ interface PaginationProps {
2
+ currentPage: number;
3
+ totalPages: number;
4
+ onPageChange: (page: number) => void;
5
+ }
6
+
7
+ export default function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
8
+ if (totalPages <= 1) return null;
9
+
10
+ return (
11
+ <div className="flex-shrink-0 border-t border-gray-200 p-3 bg-gray-50">
12
+ <div className="flex items-center justify-between text-xs">
13
+ <button
14
+ onClick={() => onPageChange(Math.max(1, currentPage - 1))}
15
+ disabled={currentPage === 1}
16
+ className="px-3 py-1.5 text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
17
+ >
18
+ Previous
19
+ </button>
20
+ <span className="text-gray-600">
21
+ Page {currentPage} of {totalPages}
22
+ </span>
23
+ <button
24
+ onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
25
+ disabled={currentPage === totalPages}
26
+ className="px-3 py-1.5 text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
27
+ >
28
+ Next
29
+ </button>
30
+ </div>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,91 @@
1
+ import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
2
+ import { ServerIcon } from '@heroicons/react/20/solid';
3
+ import { buildUrl } from '@utils/url-builder';
4
+ import type { SchemaItem, Producer, Consumer } from './types';
5
+
6
+ interface ProducersConsumersSectionProps {
7
+ message: SchemaItem;
8
+ isExpanded: boolean;
9
+ onToggle: () => void;
10
+ }
11
+
12
+ export default function ProducersConsumersSection({ message, isExpanded, onToggle }: ProducersConsumersSectionProps) {
13
+ const producers = message.data.producers || [];
14
+ const consumers = message.data.consumers || [];
15
+ const totalCount = producers.length + consumers.length;
16
+
17
+ if (totalCount === 0) return null;
18
+
19
+ return (
20
+ <div className="flex-shrink-0 border-b border-gray-200">
21
+ <button
22
+ onClick={onToggle}
23
+ className="w-full flex items-center justify-between px-4 py-1.5 text-left hover:bg-gray-50 transition-colors"
24
+ >
25
+ <div className="flex items-center gap-2">
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ className="h-4 w-4 text-gray-600"
29
+ fill="none"
30
+ viewBox="0 0 24 24"
31
+ stroke="currentColor"
32
+ >
33
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
34
+ </svg>
35
+ <span className="text-xs font-semibold text-gray-900">Producers & Consumers</span>
36
+ <span className="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">
37
+ {totalCount} services
38
+ </span>
39
+ </div>
40
+ {isExpanded ? <ChevronUpIcon className="h-4 w-4 text-gray-600" /> : <ChevronDownIcon className="h-4 w-4 text-gray-600" />}
41
+ </button>
42
+
43
+ {isExpanded && (
44
+ <div className="px-4 pb-2 bg-gray-50">
45
+ {producers.length > 0 && (
46
+ <div className="mb-3">
47
+ <h4 className="text-xs font-semibold text-gray-900 mb-2">Producers ({producers.length})</h4>
48
+ <div className="flex flex-wrap gap-2">
49
+ {producers.map((producer: Producer, idx: number) => (
50
+ <a
51
+ key={`${producer.id}-${idx}`}
52
+ href={buildUrl(`/docs/services/${producer.id}/${producer.version}`)}
53
+ className="inline-flex items-center gap-1.5 pl-1 pr-3 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-200 rounded-full hover:border-gray-300 hover:shadow-sm transition-all"
54
+ title={`View ${producer.id}`}
55
+ >
56
+ <div className="flex items-center justify-center w-5 h-5 bg-gradient-to-b from-pink-500 to-pink-600 rounded-full">
57
+ <ServerIcon className="h-3 w-3 text-white" />
58
+ </div>
59
+ <span className="font-medium">{producer.id}</span>
60
+ <span className="text-gray-500 text-[11px]">v{producer.version}</span>
61
+ </a>
62
+ ))}
63
+ </div>
64
+ </div>
65
+ )}
66
+ {consumers.length > 0 && (
67
+ <div>
68
+ <h4 className="text-xs font-semibold text-gray-900 mb-2">Consumers ({consumers.length})</h4>
69
+ <div className="flex flex-wrap gap-2">
70
+ {consumers.map((consumer: Consumer, idx: number) => (
71
+ <a
72
+ key={`${consumer.id}-${idx}`}
73
+ href={buildUrl(`/docs/services/${consumer.id}/${consumer.version}`)}
74
+ className="inline-flex items-center gap-1.5 pl-1 pr-3 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-200 rounded-full hover:border-gray-300 hover:shadow-sm transition-all"
75
+ title={`View ${consumer.id}`}
76
+ >
77
+ <div className="flex items-center justify-center w-5 h-5 bg-gradient-to-b from-pink-500 to-pink-600 rounded-full">
78
+ <ServerIcon className="h-3 w-3 text-white" />
79
+ </div>
80
+ <span className="font-medium">{consumer.id}</span>
81
+ <span className="text-gray-500 text-[11px]">v{consumer.version}</span>
82
+ </a>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ )}
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,93 @@
1
+ import * as Dialog from '@radix-ui/react-dialog';
2
+ import { XMarkIcon, ArrowsPointingOutIcon, ClipboardDocumentIcon } from '@heroicons/react/24/outline';
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4
+ import { oneLight as syntaxHighlighterStyle } from 'react-syntax-highlighter/dist/cjs/styles/prism';
5
+ import { getLanguageForHighlight } from './utils';
6
+ import type { SchemaItem } from './types';
7
+
8
+ interface SchemaCodeModalProps {
9
+ isOpen: boolean;
10
+ onOpenChange: (open: boolean) => void;
11
+ message: SchemaItem;
12
+ onCopy: () => void;
13
+ isCopied: boolean;
14
+ }
15
+
16
+ export default function SchemaCodeModal({ isOpen, onOpenChange, message, onCopy, isCopied }: SchemaCodeModalProps) {
17
+ if (!message.schemaContent) return null;
18
+
19
+ return (
20
+ <Dialog.Root open={isOpen} onOpenChange={onOpenChange}>
21
+ <Dialog.Portal>
22
+ <Dialog.Overlay className="fixed inset-0 bg-black/50 data-[state=open]:animate-overlayShow z-50" />
23
+ <Dialog.Content className="fixed inset-4 md:inset-8 rounded-lg bg-white shadow-xl focus:outline-none data-[state=open]:animate-contentShow z-[100] flex flex-col">
24
+ {/* Header */}
25
+ <div className="flex items-center justify-between p-6 border-b border-gray-200 flex-shrink-0">
26
+ <div className="flex items-center gap-3">
27
+ <ArrowsPointingOutIcon className="h-6 w-6 text-gray-500" />
28
+ <div>
29
+ <Dialog.Title className="text-xl font-semibold text-gray-900">{message.data.name}</Dialog.Title>
30
+ <Dialog.Description className="text-sm text-gray-600 mt-1">
31
+ v{message.data.version} · {getLanguageForHighlight(message.schemaExtension).toUpperCase()}
32
+ </Dialog.Description>
33
+ </div>
34
+ </div>
35
+ <div className="flex items-center gap-2">
36
+ <button
37
+ onClick={onCopy}
38
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 rounded-md transition-colors"
39
+ title="Copy code"
40
+ >
41
+ <ClipboardDocumentIcon className="h-4 w-4" />
42
+ {isCopied ? 'Copied!' : 'Copy'}
43
+ </button>
44
+ <Dialog.Close asChild>
45
+ <button
46
+ type="button"
47
+ className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
48
+ aria-label="Close"
49
+ >
50
+ <XMarkIcon className="h-6 w-6" />
51
+ </button>
52
+ </Dialog.Close>
53
+ </div>
54
+ </div>
55
+
56
+ {/* Content */}
57
+ <div className="flex-1 overflow-auto p-6">
58
+ <SyntaxHighlighter
59
+ language={getLanguageForHighlight(message.schemaExtension)}
60
+ style={syntaxHighlighterStyle}
61
+ customStyle={{
62
+ margin: 0,
63
+ padding: '1rem',
64
+ borderRadius: '0.5rem',
65
+ fontSize: '0.875rem',
66
+ lineHeight: '1.6',
67
+ height: '100%',
68
+ }}
69
+ className="bg-white border border-gray-200 rounded-lg"
70
+ showLineNumbers={true}
71
+ wrapLines={true}
72
+ wrapLongLines={true}
73
+ >
74
+ {message.schemaContent}
75
+ </SyntaxHighlighter>
76
+ </div>
77
+
78
+ {/* Footer */}
79
+ <div className="flex justify-end p-4 border-t border-gray-200 flex-shrink-0">
80
+ <Dialog.Close asChild>
81
+ <button
82
+ type="button"
83
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors"
84
+ >
85
+ Close
86
+ </button>
87
+ </Dialog.Close>
88
+ </div>
89
+ </Dialog.Content>
90
+ </Dialog.Portal>
91
+ </Dialog.Root>
92
+ );
93
+ }
@@ -0,0 +1,130 @@
1
+ import { ClipboardDocumentIcon, ArrowsPointingOutIcon } from '@heroicons/react/24/outline';
2
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3
+ import { oneLight as syntaxHighlighterStyle } from 'react-syntax-highlighter/dist/cjs/styles/prism';
4
+ import { buildUrl } from '@utils/url-builder';
5
+ import JSONSchemaViewer from './JSONSchemaViewer';
6
+ import AvroSchemaViewer from './AvroSchemaViewer';
7
+ import { getLanguageForHighlight } from './utils';
8
+ import type { SchemaItem } from './types';
9
+
10
+ interface SchemaContentViewerProps {
11
+ message: SchemaItem;
12
+ onCopy: () => void;
13
+ isCopied: boolean;
14
+ viewMode: 'code' | 'schema' | 'diff';
15
+ parsedSchema: any;
16
+ parsedAvroSchema?: any;
17
+ onOpenFullscreen?: () => void;
18
+ }
19
+
20
+ export default function SchemaContentViewer({
21
+ message,
22
+ onCopy,
23
+ isCopied,
24
+ viewMode,
25
+ parsedSchema,
26
+ parsedAvroSchema,
27
+ onOpenFullscreen,
28
+ }: SchemaContentViewerProps) {
29
+ if (!message.schemaContent) {
30
+ return (
31
+ <div className="flex items-center justify-center h-full text-gray-500">
32
+ <p className="text-sm">No schema content available</p>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ // Render schema viewer based on schema type
38
+ if (viewMode === 'schema') {
39
+ if (parsedAvroSchema) {
40
+ return <AvroSchemaViewer schema={parsedAvroSchema} onOpenFullscreen={onOpenFullscreen} />;
41
+ }
42
+ if (parsedSchema) {
43
+ return <JSONSchemaViewer schema={parsedSchema} onOpenFullscreen={onOpenFullscreen} />;
44
+ }
45
+ }
46
+
47
+ return (
48
+ <div className="h-full overflow-auto p-3 relative bg-white border border-gray-200 rounded-lg">
49
+ <div className="absolute top-5 right-5 z-10 flex items-center gap-2">
50
+ {message.collection === 'services' &&
51
+ (() => {
52
+ const specType = message.specType || 'openapi';
53
+ const specFilename = message.specFilenameWithoutExtension || 'schema';
54
+
55
+ // Determine the URL path segment based on spec type
56
+ let urlSegment = 'spec';
57
+ if (specType === 'asyncapi') {
58
+ urlSegment = 'asyncapi';
59
+ } else if (specType === 'graphql') {
60
+ urlSegment = 'graphql';
61
+ }
62
+
63
+ const specUrl = buildUrl(`/docs/services/${message.data.id}/${message.data.version}/${urlSegment}/${specFilename}`);
64
+
65
+ return (
66
+ <a
67
+ href={specUrl}
68
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 rounded-md transition-colors shadow-sm"
69
+ title="View full specification"
70
+ >
71
+ <svg
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ className="h-3.5 w-3.5"
74
+ fill="none"
75
+ viewBox="0 0 24 24"
76
+ stroke="currentColor"
77
+ >
78
+ <path
79
+ strokeLinecap="round"
80
+ strokeLinejoin="round"
81
+ strokeWidth={2}
82
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
83
+ />
84
+ </svg>
85
+ View Spec
86
+ </a>
87
+ );
88
+ })()}
89
+ {onOpenFullscreen && (
90
+ <button
91
+ onClick={onOpenFullscreen}
92
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 rounded-md transition-colors shadow-sm"
93
+ title="Open in fullscreen"
94
+ >
95
+ <ArrowsPointingOutIcon className="h-3.5 w-3.5" />
96
+ Fullscreen
97
+ </button>
98
+ )}
99
+ <button
100
+ onClick={onCopy}
101
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 rounded-md transition-colors shadow-sm"
102
+ title="Copy code"
103
+ >
104
+ <ClipboardDocumentIcon className="h-3.5 w-3.5" />
105
+ {isCopied ? 'Copied!' : 'Copy'}
106
+ </button>
107
+ </div>
108
+ <SyntaxHighlighter
109
+ language={getLanguageForHighlight(message.schemaExtension)}
110
+ style={syntaxHighlighterStyle}
111
+ customStyle={{
112
+ margin: 0,
113
+ padding: '0.75rem',
114
+ paddingTop: '2.5rem',
115
+ borderRadius: '0.5rem',
116
+ fontSize: '0.875rem',
117
+ lineHeight: '1.6',
118
+ height: '100%',
119
+ overflow: 'auto',
120
+ }}
121
+ className="bg-white border border-gray-200 rounded-lg"
122
+ showLineNumbers={true}
123
+ wrapLines={true}
124
+ wrapLongLines={true}
125
+ >
126
+ {message.schemaContent}
127
+ </SyntaxHighlighter>
128
+ </div>
129
+ );
130
+ }
@@ -0,0 +1,181 @@
1
+ import { ClipboardDocumentIcon, ArrowDownTrayIcon, CodeBracketIcon, TableCellsIcon } from '@heroicons/react/24/outline';
2
+ import { buildUrl } from '@utils/url-builder';
3
+ import { getCollectionStyles } from '@components/Grids/utils';
4
+ import { getSchemaTypeLabel } from './utils';
5
+ import type { SchemaItem } from './types';
6
+
7
+ interface SchemaDetailsHeaderProps {
8
+ message: SchemaItem;
9
+ availableVersions: SchemaItem[];
10
+ selectedVersion: string | null;
11
+ onVersionChange: (version: string) => void;
12
+ onCopy: () => void;
13
+ onDownload: () => void;
14
+ isCopied: boolean;
15
+ schemaViewMode: 'code' | 'schema' | 'diff';
16
+ onViewModeChange: (mode: 'code' | 'schema' | 'diff') => void;
17
+ hasParsedSchema: boolean;
18
+ hasDiffs: boolean;
19
+ diffCount: number;
20
+ }
21
+
22
+ export default function SchemaDetailsHeader({
23
+ message,
24
+ availableVersions,
25
+ selectedVersion,
26
+ onVersionChange,
27
+ onCopy,
28
+ onDownload,
29
+ isCopied,
30
+ schemaViewMode,
31
+ onViewModeChange,
32
+ hasParsedSchema,
33
+ hasDiffs,
34
+ diffCount,
35
+ }: SchemaDetailsHeaderProps) {
36
+ const { color, Icon } = getCollectionStyles(message.collection);
37
+ const hasMultipleVersions = availableVersions.length > 1;
38
+
39
+ return (
40
+ <div className="flex-shrink-0 border-b border-gray-200 p-3">
41
+ <div className="flex items-start justify-between mb-3">
42
+ <div className="flex-1 min-w-0">
43
+ <div className="flex items-center gap-2 mb-1.5">
44
+ <Icon className={`h-5 w-5 text-${color}-500 flex-shrink-0`} />
45
+ <a
46
+ href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
47
+ className={`text-lg font-semibold text-gray-900 hover:text-${color}-600 hover:underline truncate`}
48
+ >
49
+ {message.data.name}
50
+ </a>
51
+ {hasMultipleVersions ? (
52
+ <select
53
+ value={selectedVersion || message.data.version}
54
+ onChange={(e) => onVersionChange(e.target.value)}
55
+ className="text-xs text-gray-700 bg-white border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
56
+ >
57
+ {availableVersions.map((v) => (
58
+ <option key={v.data.version} value={v.data.version}>
59
+ v{v.data.version}
60
+ </option>
61
+ ))}
62
+ </select>
63
+ ) : (
64
+ <span className="text-xs text-gray-500 flex-shrink-0">v{message.data.version}</span>
65
+ )}
66
+ </div>
67
+ <div className="flex items-center gap-1 mb-2">
68
+ <span
69
+ className={`inline-flex items-center rounded-full bg-${color}-100 px-1.5 py-0.5 text-xs font-medium text-${color}-800`}
70
+ >
71
+ {message.collection}
72
+ </span>
73
+ <span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800">
74
+ {(() => {
75
+ const ext = message.schemaExtension?.toLowerCase();
76
+ if (
77
+ ext === 'openapi' ||
78
+ ext === 'asyncapi' ||
79
+ ext === 'graphql' ||
80
+ ext === 'avro' ||
81
+ ext === 'json' ||
82
+ ext === 'proto'
83
+ ) {
84
+ // Map json extension to json-schema icon
85
+ const iconName = ext === 'json' ? 'json-schema' : ext;
86
+ const iconPath = buildUrl(`/icons/${iconName}.svg`, true);
87
+ return (
88
+ <>
89
+ <img src={iconPath} alt={`${ext} icon`} className="h-3 w-3" />
90
+ {getSchemaTypeLabel(message.schemaExtension)}
91
+ </>
92
+ );
93
+ }
94
+ return getSchemaTypeLabel(message.schemaExtension);
95
+ })()}
96
+ </span>
97
+ </div>
98
+ {message.data.summary && <p className="text-xs text-gray-600 line-clamp-2">{message.data.summary}</p>}
99
+ </div>
100
+ </div>
101
+
102
+ {/* Action Buttons */}
103
+ <div className="flex items-center gap-2">
104
+ {/* View Mode Toggle */}
105
+ <div className="flex items-center gap-1 mr-2 border-r border-gray-300 pr-2">
106
+ <button
107
+ onClick={() => onViewModeChange('code')}
108
+ className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded transition-colors ${
109
+ schemaViewMode === 'code' ? 'bg-primary text-white' : 'text-gray-600 hover:bg-gray-100'
110
+ }`}
111
+ title="Code view"
112
+ >
113
+ <CodeBracketIcon className="h-3.5 w-3.5" />
114
+ Code
115
+ </button>
116
+ {hasParsedSchema && (
117
+ <button
118
+ onClick={() => onViewModeChange('schema')}
119
+ className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded transition-colors ${
120
+ schemaViewMode === 'schema' ? 'bg-primary text-white' : 'text-gray-600 hover:bg-gray-100'
121
+ }`}
122
+ title="Schema view"
123
+ >
124
+ <TableCellsIcon className="h-3.5 w-3.5" />
125
+ Schema
126
+ </button>
127
+ )}
128
+ {hasDiffs && (
129
+ <button
130
+ onClick={() => onViewModeChange('diff')}
131
+ className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded transition-colors ${
132
+ schemaViewMode === 'diff' ? 'bg-primary text-white' : 'text-gray-600 hover:bg-gray-100'
133
+ }`}
134
+ title="View version history diffs"
135
+ >
136
+ <svg
137
+ xmlns="http://www.w3.org/2000/svg"
138
+ className="h-3.5 w-3.5"
139
+ fill="none"
140
+ viewBox="0 0 24 24"
141
+ stroke="currentColor"
142
+ >
143
+ <path
144
+ strokeLinecap="round"
145
+ strokeLinejoin="round"
146
+ strokeWidth={2}
147
+ d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
148
+ />
149
+ </svg>
150
+ Diff ({diffCount})
151
+ </button>
152
+ )}
153
+ </div>
154
+
155
+ <button
156
+ onClick={onCopy}
157
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
158
+ title="Copy schema to clipboard"
159
+ >
160
+ <ClipboardDocumentIcon className="h-4 w-4" />
161
+ {isCopied ? 'Copied!' : 'Copy'}
162
+ </button>
163
+ <button
164
+ onClick={onDownload}
165
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
166
+ title="Download schema file"
167
+ >
168
+ <ArrowDownTrayIcon className="h-4 w-4" />
169
+ Download
170
+ </button>
171
+ <a
172
+ href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
173
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors ml-auto"
174
+ title="View full documentation"
175
+ >
176
+ View Docs →
177
+ </a>
178
+ </div>
179
+ </div>
180
+ );
181
+ }