@eventcatalog/core 3.29.2 → 3.30.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 (79) 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-36IA4UE4.js → chunk-6UG4JMUV.js} +1 -1
  6. package/dist/{chunk-MEJOYC5Z.js → chunk-ATRBVTJ6.js} +1 -1
  7. package/dist/{chunk-VEUNSJ6Z.js → chunk-MVZKHUX2.js} +1 -1
  8. package/dist/{chunk-DB4IQ3GB.js → chunk-RRBDF4MM.js} +1 -1
  9. package/dist/{chunk-EGQGCB2B.js → chunk-Z26P4PCB.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/astro.config.mjs +1 -1
  19. package/eventcatalog/public/logo.png +0 -0
  20. package/eventcatalog/src/components/CopyAsMarkdown.tsx +2 -2
  21. package/eventcatalog/src/components/EnvironmentDropdown.tsx +33 -21
  22. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +3 -53
  23. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +144 -91
  24. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +112 -109
  25. package/eventcatalog/src/components/Header.astro +9 -19
  26. package/eventcatalog/src/components/MDX/Accordion/Accordion.tsx +12 -14
  27. package/eventcatalog/src/components/MDX/Accordion/AccordionGroup.astro +11 -3
  28. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +15 -5
  29. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +164 -53
  30. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +1 -1
  31. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +4 -4
  32. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +12 -10
  33. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +48 -77
  34. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +238 -169
  35. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +189 -230
  36. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +39 -36
  37. package/eventcatalog/src/components/Search/Search.astro +1 -1
  38. package/eventcatalog/src/components/Seo.astro +1 -1
  39. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +3 -3
  40. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +229 -256
  41. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +78 -59
  42. package/eventcatalog/src/components/Tables/Discover/columns.tsx +130 -197
  43. package/eventcatalog/src/components/Tables/Table.tsx +21 -18
  44. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +79 -131
  45. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +104 -175
  46. package/eventcatalog/src/enterprise/auth/error.astro +1 -1
  47. package/eventcatalog/src/enterprise/auth/login.astro +1 -1
  48. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +95 -93
  49. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +174 -136
  50. package/eventcatalog/src/enterprise/fields/pages/fields.astro +10 -8
  51. package/eventcatalog/src/enterprise/integrations/eventcatalog-features.ts +0 -8
  52. package/eventcatalog/src/layouts/DirectoryLayout.astro +17 -88
  53. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +528 -146
  54. package/eventcatalog/src/layouts/VisualiserLayout.astro +7 -2
  55. package/eventcatalog/src/pages/_index.astro +5 -3
  56. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -3
  57. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +223 -73
  58. package/eventcatalog/src/pages/discover/[type]/index.astro +22 -141
  59. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +129 -29
  60. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +129 -29
  61. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +6 -2
  62. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +2 -2
  63. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +21 -18
  64. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +33 -32
  65. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +5 -1
  66. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +2 -2
  67. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +4 -6
  68. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +11 -4
  69. package/eventcatalog/src/pages/docs/users/[id]/index.astro +11 -4
  70. package/eventcatalog/src/pages/schemas/explorer/index.astro +10 -8
  71. package/eventcatalog/src/pages/studio.astro +1 -1
  72. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +2 -7
  73. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  74. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +2 -7
  75. package/eventcatalog/src/styles/theme.css +68 -12
  76. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +13 -0
  77. package/package.json +1 -1
  78. package/eventcatalog/public/logo.svg +0 -14
  79. package/eventcatalog/src/enterprise/plans/index.astro +0 -319
@@ -1,10 +1,11 @@
1
- import { useState, useMemo } from 'react';
1
+ import { useState, useMemo, useEffect, useRef } from 'react';
2
2
  import * as Diff from 'diff';
3
3
  import { html } from 'diff2html';
4
4
  import 'diff2html/bundles/css/diff2html.min.css';
5
5
  import {
6
6
  ArrowDownTrayIcon,
7
7
  ArrowTopRightOnSquareIcon,
8
+ ChevronDownIcon,
8
9
  ClipboardDocumentIcon,
9
10
  TableCellsIcon,
10
11
  CodeBracketIcon,
@@ -50,6 +51,8 @@ export default function SchemaDetailsPanel({
50
51
  const [isDiffModalOpen, setIsDiffModalOpen] = useState(false);
51
52
  const [isCodeModalOpen, setIsCodeModalOpen] = useState(false);
52
53
  const [isSchemaViewerModalOpen, setIsSchemaViewerModalOpen] = useState(false);
54
+ const [isActionsOpen, setIsActionsOpen] = useState(false);
55
+ const actionsMenuRef = useRef<HTMLDivElement>(null);
53
56
 
54
57
  const hasMultipleVersions = availableVersions.length > 1;
55
58
  const { color } = getCollectionStyles(message.collection);
@@ -58,6 +61,7 @@ export default function SchemaDetailsPanel({
58
61
  const owners = message.data.owners || [];
59
62
  const producers = message.data.producers || [];
60
63
  const consumers = message.data.consumers || [];
64
+ const filename = message.data.schemaPath?.split('/').pop() || `${message.data.id}.${ext || 'json'}`;
61
65
 
62
66
  // Generate diffs between all consecutive versions
63
67
  const allDiffs: VersionDiff[] = useMemo(() => {
@@ -155,6 +159,30 @@ export default function SchemaDetailsPanel({
155
159
  };
156
160
 
157
161
  const isCopied = copiedId === message.data.id;
162
+ const docsUrl = buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`);
163
+ const sidebarCardClass = 'rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg)/0.66)] px-4 py-4';
164
+
165
+ useEffect(() => {
166
+ const handlePointerDown = (event: MouseEvent) => {
167
+ if (!actionsMenuRef.current?.contains(event.target as Node)) {
168
+ setIsActionsOpen(false);
169
+ }
170
+ };
171
+
172
+ const handleEscape = (event: KeyboardEvent) => {
173
+ if (event.key === 'Escape') {
174
+ setIsActionsOpen(false);
175
+ }
176
+ };
177
+
178
+ document.addEventListener('mousedown', handlePointerDown);
179
+ document.addEventListener('keydown', handleEscape);
180
+
181
+ return () => {
182
+ document.removeEventListener('mousedown', handlePointerDown);
183
+ document.removeEventListener('keydown', handleEscape);
184
+ };
185
+ }, []);
158
186
 
159
187
  const hasParsedSchema = !!parsedSchema || !!parsedAvroSchema;
160
188
  // Build tabs
@@ -174,28 +202,95 @@ export default function SchemaDetailsPanel({
174
202
  tabs.push({ id: 'api', label: 'API', icon: <GlobeAltIcon className="h-3.5 w-3.5" /> });
175
203
 
176
204
  return (
177
- <div className="h-full flex bg-[rgb(var(--ec-page-bg))] overflow-hidden">
205
+ <div className="flex h-full min-h-0 bg-[rgb(var(--ec-page-bg))] overflow-hidden">
178
206
  {/* Left: header + tabs + content */}
179
- <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
207
+ <div className="flex-1 min-h-0 min-w-0 flex flex-col overflow-hidden">
180
208
  {/* Compact header */}
181
209
  <div className="flex-shrink-0 px-6 pt-5 pb-3">
182
- <div className="flex items-center gap-3 min-w-0">
183
- <div className="flex-shrink-0 flex items-center justify-center w-9 h-9 rounded-lg bg-[rgb(var(--ec-content-hover))] border border-[rgb(var(--ec-page-border)/0.5)]">
184
- {iconSpec ? (
185
- <img src={buildUrl(`/icons/${iconSpec}.svg`, true)} alt={`${ext} icon`} className="h-5 w-5 schema-icon" />
186
- ) : (
187
- <span className="text-xs font-bold font-mono text-[rgb(var(--ec-page-text-muted))]">{'{ }'}</span>
210
+ <div className="flex items-start justify-between gap-4">
211
+ <div className="min-w-0 flex-1">
212
+ <div className="flex min-w-0 items-center gap-3">
213
+ <div className="flex-shrink-0 flex items-center justify-center w-9 h-9 rounded-lg bg-[rgb(var(--ec-content-hover))] border border-[rgb(var(--ec-page-border)/0.5)]">
214
+ {iconSpec ? (
215
+ <img src={buildUrl(`/icons/${iconSpec}.svg`, true)} alt={`${ext} icon`} className="h-5 w-5 schema-icon" />
216
+ ) : (
217
+ <span className="text-xs font-bold font-mono text-[rgb(var(--ec-page-text-muted))]">{'{ }'}</span>
218
+ )}
219
+ </div>
220
+ <h2 className="text-xl font-semibold text-[rgb(var(--ec-page-text))] truncate">{message.data.name}</h2>
221
+ <span className="flex-shrink-0 text-xs font-mono tabular-nums text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-2 py-0.5 rounded-md">
222
+ v{message.data.version}
223
+ </span>
224
+ <span
225
+ className={`flex-shrink-0 inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[11px] font-medium bg-${color}-500/10 text-${color}-400 capitalize`}
226
+ >
227
+ {message.collection}
228
+ </span>
229
+ </div>
230
+ {message.data.summary && (
231
+ <p className="mt-3 text-sm leading-relaxed text-[rgb(var(--ec-page-text-muted))]">{message.data.summary}</p>
232
+ )}
233
+ </div>
234
+
235
+ <div className="relative flex-shrink-0" ref={actionsMenuRef}>
236
+ <div className="inline-flex overflow-hidden rounded-lg border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-content-hover))] shadow-xs">
237
+ <button
238
+ type="button"
239
+ onClick={() => setIsActionsOpen((prev) => !prev)}
240
+ aria-expanded={isActionsOpen}
241
+ aria-haspopup="menu"
242
+ className="inline-flex items-center gap-2 px-4 py-2 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.45)] hover:text-[rgb(var(--ec-accent))]"
243
+ >
244
+ Actions
245
+ </button>
246
+ <button
247
+ type="button"
248
+ onClick={() => setIsActionsOpen((prev) => !prev)}
249
+ aria-expanded={isActionsOpen}
250
+ aria-haspopup="menu"
251
+ aria-label="Open actions menu"
252
+ className="inline-flex items-center justify-center border-l border-[rgb(var(--ec-page-border))] px-2.5 py-2 text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.45)] hover:text-[rgb(var(--ec-page-text))]"
253
+ >
254
+ <ChevronDownIcon className={`h-3.5 w-3.5 transition-transform ${isActionsOpen ? 'rotate-180' : ''}`} />
255
+ </button>
256
+ </div>
257
+
258
+ {isActionsOpen && (
259
+ <div className="absolute right-0 top-[calc(100%+0.5rem)] z-20 min-w-[220px] overflow-hidden rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] shadow-xl">
260
+ <a
261
+ href={docsUrl}
262
+ className="flex items-center gap-2.5 px-3 py-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
263
+ >
264
+ <ArrowTopRightOnSquareIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
265
+ View documentation
266
+ </a>
267
+ <button
268
+ onClick={() => {
269
+ handleDownload();
270
+ setIsActionsOpen(false);
271
+ }}
272
+ className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
273
+ >
274
+ <ArrowDownTrayIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
275
+ Download schema
276
+ </button>
277
+ <button
278
+ onClick={() => {
279
+ handleCopy();
280
+ setIsActionsOpen(false);
281
+ }}
282
+ className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
283
+ >
284
+ {isCopied ? (
285
+ <CheckIcon className="h-3.5 w-3.5 text-green-400" />
286
+ ) : (
287
+ <ClipboardDocumentIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
288
+ )}
289
+ {isCopied ? 'Copied to clipboard' : 'Copy schema to clipboard'}
290
+ </button>
291
+ </div>
188
292
  )}
189
293
  </div>
190
- <h2 className="text-xl font-semibold text-[rgb(var(--ec-page-text))] truncate">{message.data.name}</h2>
191
- <span className="flex-shrink-0 text-xs font-mono tabular-nums text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-2 py-0.5 rounded-md">
192
- v{message.data.version}
193
- </span>
194
- <span
195
- className={`flex-shrink-0 inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[11px] font-medium bg-${color}-500/10 text-${color}-400 capitalize`}
196
- >
197
- {message.collection}
198
- </span>
199
294
  </div>
200
295
  </div>
201
296
 
@@ -220,7 +315,7 @@ export default function SchemaDetailsPanel({
220
315
  </div>
221
316
 
222
317
  {/* Tab content */}
223
- <div className="flex-1 overflow-hidden p-6">
318
+ <div className="flex-1 min-h-0 overflow-hidden p-6">
224
319
  {activeTab === 'examples' && examples.length > 0 ? (
225
320
  <ExamplesViewer examples={examples} />
226
321
  ) : activeTab === 'api' ? (
@@ -254,166 +349,140 @@ export default function SchemaDetailsPanel({
254
349
  </div>
255
350
 
256
351
  {/* Right sidebar - spans full height */}
257
- <div className="flex-shrink-0 w-72 border-l border-[rgb(var(--ec-page-border))] overflow-y-auto">
258
- {/* View docs link */}
259
- <div className="p-5 border-b border-[rgb(var(--ec-page-border))]">
260
- <a
261
- href={buildUrl(`/docs/${message.collection}/${message.data.id}/${message.data.version}`)}
262
- className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-medium text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-content-hover))] border border-[rgb(var(--ec-page-border))] rounded-lg hover:border-[rgb(var(--ec-accent)/0.3)] hover:text-[rgb(var(--ec-accent))] transition-all"
263
- >
264
- <ArrowTopRightOnSquareIcon className="h-3.5 w-3.5" />
265
- View docs
266
- </a>
267
- </div>
352
+ <div className="h-full w-80 flex-shrink-0 overflow-y-auto border-l border-[rgb(var(--ec-page-border))] px-3 py-4">
353
+ <div className="space-y-3">
354
+ {/* Details section */}
355
+ <div className={sidebarCardClass}>
356
+ <h3 className="mb-4 text-[0.8rem] font-semibold text-[rgb(var(--ec-page-text))]">Details</h3>
357
+ <dl className="space-y-2.5">
358
+ <div className="flex items-center justify-between">
359
+ <dt className="text-xs text-[rgb(var(--ec-page-text-muted))]">Format</dt>
360
+ <dd className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))]">
361
+ {getSchemaTypeLabel(message.schemaExtension)}
362
+ </dd>
363
+ </div>
364
+ <div className="flex items-center justify-between">
365
+ <dt className="text-xs text-[rgb(var(--ec-page-text-muted))]">Resource</dt>
366
+ <dd className={`text-xs font-medium text-${color}-400 capitalize`}>{message.collection}</dd>
367
+ </div>
368
+ <div className="flex items-center justify-between gap-3">
369
+ <dt className="text-xs text-[rgb(var(--ec-page-text-muted))]">Filename</dt>
370
+ <dd className="max-w-[11rem] truncate text-xs font-medium text-[rgb(var(--ec-page-text-muted))]" title={filename}>
371
+ {filename}
372
+ </dd>
373
+ </div>
374
+ </dl>
375
+ </div>
268
376
 
269
- {/* Details section */}
270
- <div className="p-5 border-b border-[rgb(var(--ec-page-border))]">
271
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-4">Details</h3>
272
- <dl className="space-y-3">
273
- <div className="flex items-center justify-between">
274
- <dt className="text-xs text-[rgb(var(--ec-page-text-muted))]">Format</dt>
275
- <dd className="text-xs font-medium text-[rgb(var(--ec-page-text))]">
276
- {getSchemaTypeLabel(message.schemaExtension)}
277
- </dd>
377
+ {/* Versions section */}
378
+ <div className={sidebarCardClass}>
379
+ <div className="mb-3 flex items-center justify-between gap-3">
380
+ <h3 className="text-[0.8rem] font-semibold text-[rgb(var(--ec-page-text))]">Versions</h3>
278
381
  </div>
279
- <div className="flex items-center justify-between">
280
- <dt className="text-xs text-[rgb(var(--ec-page-text-muted))]">Resource</dt>
281
- <dd className={`text-xs font-medium text-${color}-400 capitalize`}>{message.collection}</dd>
382
+ <div className="space-y-1">
383
+ {availableVersions
384
+ .filter((v, idx, arr) => arr.findIndex((a) => a.data.version === v.data.version) === idx)
385
+ .map((v, idx) => {
386
+ const isActive = v.data.version === message.data.version;
387
+ const isLatest = idx === 0;
388
+ return (
389
+ <button
390
+ key={`${v.data.version}-${idx}`}
391
+ onClick={() => onVersionChange(v.data.version)}
392
+ className={`w-full flex items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors ${
393
+ isActive
394
+ ? 'bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-page-text))]'
395
+ : 'text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-page-bg)/0.55)]'
396
+ }`}
397
+ >
398
+ <div
399
+ className={`w-2 h-2 rounded-full flex-shrink-0 ${
400
+ isActive ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'
401
+ }`}
402
+ />
403
+ <span className="text-xs font-mono tabular-nums">v{v.data.version}</span>
404
+ {isLatest && (
405
+ <span className="text-[10px] font-medium text-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent-subtle))] px-1.5 py-0.5 rounded">
406
+ latest
407
+ </span>
408
+ )}
409
+ </button>
410
+ );
411
+ })}
282
412
  </div>
283
- {message.data.summary && (
284
- <div>
285
- <dt className="text-xs text-[rgb(var(--ec-page-text-muted))] mb-1">Summary</dt>
286
- <dd className="text-xs text-[rgb(var(--ec-page-text))] leading-relaxed">{message.data.summary}</dd>
413
+ </div>
414
+
415
+ {/* Schema producers section */}
416
+ {showProducersConsumers && producers.length > 0 && message.collection !== 'services' && (
417
+ <div className={sidebarCardClass}>
418
+ <div className="mb-2.5 flex items-center justify-between gap-3">
419
+ <h3 className="text-[0.8rem] font-semibold text-[rgb(var(--ec-page-text))]">
420
+ Schema Producers ({producers.length})
421
+ </h3>
287
422
  </div>
288
- )}
289
- </dl>
290
- </div>
423
+ <div className="space-y-1">
424
+ {producers.map((producer: Producer, idx: number) => {
425
+ const serviceName = extractServiceName(producer.id);
426
+ return (
427
+ <a
428
+ key={`${producer.id}-${idx}`}
429
+ href={buildUrl(`/docs/services/${serviceName}/${producer.version}`)}
430
+ className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.55)] hover:text-[rgb(var(--ec-accent))]"
431
+ >
432
+ <ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
433
+ {serviceName}
434
+ </a>
435
+ );
436
+ })}
437
+ </div>
438
+ </div>
439
+ )}
291
440
 
292
- {/* Producers section */}
293
- {showProducersConsumers && producers.length > 0 && message.collection !== 'services' && (
294
- <div className="px-5 py-3 border-b border-[rgb(var(--ec-page-border))]">
295
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-2">Producers</h3>
296
- <div>
297
- {producers.map((producer: Producer, idx: number) => {
298
- const serviceName = extractServiceName(producer.id);
299
- return (
300
- <a
301
- key={`${producer.id}-${idx}`}
302
- href={buildUrl(`/docs/services/${serviceName}/${producer.version}`)}
303
- className="flex items-center gap-2 px-2 py-1 rounded-md text-xs font-medium text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))] transition-colors"
304
- >
305
- <ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
306
- {serviceName}
307
- </a>
308
- );
309
- })}
441
+ {/* Schema consumers section */}
442
+ {showProducersConsumers && consumers.length > 0 && message.collection !== 'services' && (
443
+ <div className={sidebarCardClass}>
444
+ <div className="mb-2.5 flex items-center justify-between gap-3">
445
+ <h3 className="text-[0.8rem] font-semibold text-[rgb(var(--ec-page-text))]">
446
+ Schema Consumers ({consumers.length})
447
+ </h3>
448
+ </div>
449
+ <div className="space-y-1">
450
+ {consumers.map((consumer: Consumer, idx: number) => {
451
+ const serviceName = extractServiceName(consumer.id);
452
+ return (
453
+ <a
454
+ key={`${consumer.id}-${idx}`}
455
+ href={buildUrl(`/docs/services/${serviceName}/${consumer.version}`)}
456
+ className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.55)] hover:text-[rgb(var(--ec-accent))]"
457
+ >
458
+ <ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
459
+ {serviceName}
460
+ </a>
461
+ );
462
+ })}
463
+ </div>
310
464
  </div>
311
- </div>
312
- )}
313
-
314
- {/* Consumers section */}
315
- {showProducersConsumers && consumers.length > 0 && message.collection !== 'services' && (
316
- <div className="px-5 py-3 border-b border-[rgb(var(--ec-page-border))]">
317
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-2">Consumers</h3>
318
- <div>
319
- {consumers.map((consumer: Consumer, idx: number) => {
320
- const serviceName = extractServiceName(consumer.id);
321
- return (
465
+ )}
466
+
467
+ {/* Owners section */}
468
+ {showOwners && owners.length > 0 && (
469
+ <div className={sidebarCardClass}>
470
+ <div className="mb-3 flex items-center justify-between gap-3">
471
+ <h3 className="text-[0.8rem] font-semibold text-[rgb(var(--ec-page-text))]">Owners</h3>
472
+ </div>
473
+ <div className="space-y-1">
474
+ {owners.map((owner: Owner, idx: number) => (
322
475
  <a
323
- key={`${consumer.id}-${idx}`}
324
- href={buildUrl(`/docs/services/${serviceName}/${consumer.version}`)}
325
- className="flex items-center gap-2 px-2 py-1 rounded-md text-xs font-medium text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))] transition-colors"
476
+ key={`${owner.id}-${idx}`}
477
+ href={owner.href}
478
+ className="block rounded-lg px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.55)] hover:text-[rgb(var(--ec-accent))]"
326
479
  >
327
- <ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
328
- {serviceName}
480
+ {owner.name}
329
481
  </a>
330
- );
331
- })}
332
- </div>
333
- </div>
334
- )}
335
-
336
- {/* Versions section */}
337
- <div className="p-5 border-b border-[rgb(var(--ec-page-border))]">
338
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-3">Versions</h3>
339
- <div className="space-y-1">
340
- {availableVersions
341
- .filter((v, idx, arr) => arr.findIndex((a) => a.data.version === v.data.version) === idx)
342
- .map((v, idx) => {
343
- const isActive = v.data.version === message.data.version;
344
- const isLatest = idx === 0;
345
- return (
346
- <button
347
- key={`${v.data.version}-${idx}`}
348
- onClick={() => onVersionChange(v.data.version)}
349
- className={`w-full flex items-center gap-2.5 px-2 py-1.5 rounded-md text-left transition-colors ${
350
- isActive
351
- ? 'bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-page-text))]'
352
- : 'hover:bg-[rgb(var(--ec-content-hover))] text-[rgb(var(--ec-page-text-muted))]'
353
- }`}
354
- >
355
- <div
356
- className={`w-2 h-2 rounded-full flex-shrink-0 ${
357
- isActive ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'
358
- }`}
359
- />
360
- <span className="text-xs font-mono tabular-nums">v{v.data.version}</span>
361
- {isLatest && (
362
- <span className="text-[10px] font-medium text-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent-subtle))] px-1.5 py-0.5 rounded">
363
- latest
364
- </span>
365
- )}
366
- </button>
367
- );
368
- })}
369
- </div>
370
- </div>
371
-
372
- {/* Owners section */}
373
- {showOwners && owners.length > 0 && (
374
- <div className="p-5 border-b border-[rgb(var(--ec-page-border))]">
375
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-3">Owners</h3>
376
- <div className="space-y-1">
377
- {owners.map((owner: Owner, idx: number) => (
378
- <a
379
- key={`${owner.id}-${idx}`}
380
- href={owner.href}
381
- className="block px-2 py-1.5 rounded-md text-xs font-medium text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))] transition-colors"
382
- >
383
- {owner.name}
384
- </a>
385
- ))}
482
+ ))}
483
+ </div>
386
484
  </div>
387
- </div>
388
- )}
389
-
390
- {/* Downloads section */}
391
- <div className="p-5">
392
- <h3 className="text-xs font-medium text-[rgb(var(--ec-page-text-muted))] uppercase tracking-wider mb-3">Downloads</h3>
393
- <div className="space-y-2">
394
- <button
395
- onClick={handleDownload}
396
- className="w-full flex items-center gap-2.5 px-3 py-2 text-xs font-medium text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-content-hover))] border border-[rgb(var(--ec-page-border))] rounded-lg hover:border-[rgb(var(--ec-accent)/0.3)] transition-all"
397
- >
398
- <ArrowDownTrayIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
399
- Download schema file
400
- </button>
401
- <button
402
- onClick={handleCopy}
403
- className={`w-full flex items-center gap-2.5 px-3 py-2 text-xs font-medium border rounded-lg transition-all ${
404
- isCopied
405
- ? 'bg-green-500/10 text-green-400 border-green-500/30'
406
- : 'text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-content-hover))] border-[rgb(var(--ec-page-border))] hover:border-[rgb(var(--ec-accent)/0.3)]'
407
- }`}
408
- >
409
- {isCopied ? (
410
- <CheckIcon className="h-3.5 w-3.5" />
411
- ) : (
412
- <ClipboardDocumentIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
413
- )}
414
- {isCopied ? 'Copied to clipboard' : 'Copy schema content'}
415
- </button>
416
- </div>
485
+ )}
417
486
  </div>
418
487
  </div>
419
488