@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,23 +1,29 @@
1
1
  import { useState, useMemo, useEffect, useRef } from 'react';
2
- import { DocumentTextIcon, MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline';
3
- import {
4
- BoltIcon,
5
- ChatBubbleLeftIcon,
6
- MagnifyingGlassIcon as MagnifyingGlassSolidIcon,
7
- CodeBracketIcon,
8
- DocumentCheckIcon,
9
- } from '@heroicons/react/24/solid';
2
+ import { AdjustmentsHorizontalIcon, DocumentTextIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
10
3
  import type { CollectionMessageTypes } from '@types';
11
-
12
- // Specification file types (OpenAPI, AsyncAPI, GraphQL)
13
- const SPEC_TYPES = ['openapi', 'asyncapi', 'graphql'];
14
- const HIDDEN_FORMAT_FILTERS = new Set(['graphql', 'gql', 'yaml', 'yml']);
15
4
  import semver from 'semver';
16
5
  import SchemaListItem from './SchemaListItem';
17
6
  import SchemaDetailsPanel from './SchemaDetailsPanel';
18
7
  import Pagination from './Pagination';
19
8
  import type { SchemaItem } from './types';
20
9
 
10
+ // Specification file types (OpenAPI, AsyncAPI, GraphQL)
11
+ const SPEC_TYPES = ['openapi', 'asyncapi', 'graphql'];
12
+ const HIDDEN_FORMAT_FILTERS = new Set(['graphql', 'gql', 'yaml', 'yml']);
13
+ const SCHEMA_TYPE_LABELS: Record<string, string> = {
14
+ json: 'JSON Schema',
15
+ asyncapi: 'AsyncAPI',
16
+ openapi: 'OpenAPI',
17
+ graphql: 'GraphQL',
18
+ avro: 'Avro',
19
+ avsc: 'Avro',
20
+ proto: 'Protobuf',
21
+ yaml: 'YAML',
22
+ yml: 'YAML',
23
+ xml: 'XML',
24
+ xsd: 'XML Schema',
25
+ };
26
+
21
27
  /** Resolve the spec filename for a schema item, with consistent fallback. */
22
28
  function getSpecFile(item: SchemaItem): string {
23
29
  return item.specFilenameWithoutExtension || item.specName || '';
@@ -42,15 +48,14 @@ interface SchemaExplorerProps {
42
48
 
43
49
  export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: SchemaExplorerProps) {
44
50
  const [searchQuery, setSearchQuery] = useState(() => {
45
- // Load from localStorage
46
51
  if (typeof window !== 'undefined') {
47
52
  const stored = localStorage.getItem('schemaRegistrySearchQuery');
48
53
  return stored !== null ? stored : '';
49
54
  }
50
55
  return '';
51
56
  });
57
+
52
58
  const [selectedTypes, setSelectedTypes] = useState<Set<CollectionMessageTypes | 'specifications' | 'data-contracts'>>(() => {
53
- // Load from localStorage
54
59
  if (typeof window !== 'undefined') {
55
60
  const stored = localStorage.getItem('schemaRegistrySelectedTypes');
56
61
  if (stored) {
@@ -64,22 +69,30 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
64
69
  }
65
70
  return new Set();
66
71
  });
72
+
67
73
  const [selectedSchemaType, setSelectedSchemaType] = useState<'all' | string>(() => {
68
- // Load from localStorage
69
74
  if (typeof window !== 'undefined') {
70
75
  const stored = localStorage.getItem('schemaRegistrySelectedSchemaType');
71
76
  return stored !== null ? stored : 'all';
72
77
  }
73
78
  return 'all';
74
79
  });
80
+
81
+ const [showFormatFilters, setShowFormatFilters] = useState(() => {
82
+ if (typeof window !== 'undefined') {
83
+ const stored = localStorage.getItem('schemaRegistrySelectedSchemaType');
84
+ return stored !== null && stored !== 'all';
85
+ }
86
+ return false;
87
+ });
88
+
75
89
  const [selectedMessage, setSelectedMessage] = useState<SchemaItem | null>(null);
76
90
  const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
77
91
  const [currentPage, setCurrentPage] = useState(1);
78
- const searchInputRef = useRef<HTMLInputElement>(null);
79
92
  const selectedItemRef = useRef<HTMLButtonElement>(null);
93
+ const searchInputRef = useRef<HTMLInputElement>(null);
80
94
  const ITEMS_PER_PAGE = 50;
81
95
 
82
- // Function to update URL with query params
83
96
  const updateUrlParams = (message: SchemaItem) => {
84
97
  if (typeof window === 'undefined') return;
85
98
 
@@ -88,7 +101,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
88
101
  params.set('version', message.data.version);
89
102
  params.set('collection', message.collection);
90
103
 
91
- // For services, add spec type and filename to disambiguate multiple specs
92
104
  if (message.collection === 'services') {
93
105
  params.set('specType', message.specType || 'unknown');
94
106
  const specFile = getSpecFile(message);
@@ -101,12 +113,11 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
101
113
  window.history.pushState({}, '', newUrl);
102
114
  };
103
115
 
104
- // Group messages by ID (and spec type for services) and get all versions
105
116
  const messagesByIdAndVersions = useMemo(() => {
106
117
  const grouped = new Map<string, SchemaItem[]>();
118
+
107
119
  schemas.forEach((message) => {
108
120
  const groupKey = getGroupKey(message);
109
-
110
121
  const existing = grouped.get(groupKey);
111
122
  if (existing) {
112
123
  existing.push(message);
@@ -115,42 +126,36 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
115
126
  }
116
127
  });
117
128
 
118
- // Sort versions for each ID (descending - latest first)
119
129
  grouped.forEach((versions, id) => {
120
130
  versions.sort((a, b) => {
121
131
  const aVersion = a.data.version;
122
132
  const bVersion = b.data.version;
123
-
124
- // Try to use semver for comparison
125
133
  const aValid = semver.valid(semver.coerce(aVersion));
126
134
  const bValid = semver.valid(semver.coerce(bVersion));
127
135
 
128
136
  if (aValid && bValid) {
129
- return semver.rcompare(aValid, bValid); // descending order
137
+ return semver.rcompare(aValid, bValid);
130
138
  }
131
139
 
132
- // Fall back to numeric comparison
133
140
  const aNum = parseFloat(aVersion);
134
141
  const bNum = parseFloat(bVersion);
135
142
  if (!isNaN(aNum) && !isNaN(bNum)) {
136
143
  return bNum - aNum;
137
144
  }
138
145
 
139
- // Final fallback to string comparison
140
146
  return bVersion.localeCompare(aVersion);
141
147
  });
148
+
142
149
  grouped.set(id, versions);
143
150
  });
144
151
 
145
152
  return grouped;
146
153
  }, [schemas]);
147
154
 
148
- // Get latest version for each message (for sidebar display)
149
155
  const latestMessages = useMemo(() => {
150
156
  return Array.from(messagesByIdAndVersions.values()).map((versions) => versions[0]);
151
157
  }, [messagesByIdAndVersions]);
152
158
 
153
- // Get unique schema types (exclude types that aren't useful as standalone filters)
154
159
  const schemaTypes = useMemo(() => {
155
160
  const types = new Set<string>();
156
161
  latestMessages.forEach((msg) => {
@@ -161,22 +166,17 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
161
166
  return Array.from(types).sort();
162
167
  }, [latestMessages]);
163
168
 
164
- // Filter messages (using latest versions only)
165
169
  const filteredMessages = useMemo(() => {
166
170
  let result = [...latestMessages];
167
171
 
168
- // Filter by message types (multi-select)
169
172
  if (selectedTypes.size > 0) {
170
173
  result = result.filter((msg) => {
171
- // Check if message matches any selected collection type
172
174
  if (selectedTypes.has(msg.collection as CollectionMessageTypes)) {
173
175
  return true;
174
176
  }
175
- // Check if 'specifications' is selected and this is a spec file
176
177
  if (selectedTypes.has('specifications') && SPEC_TYPES.includes(msg.schemaExtension?.toLowerCase() || '')) {
177
178
  return true;
178
179
  }
179
- // Check if 'data-contracts' is selected and this is a data product contract
180
180
  if (selectedTypes.has('data-contracts') && msg.collection === 'data-products') {
181
181
  return true;
182
182
  }
@@ -184,12 +184,10 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
184
184
  });
185
185
  }
186
186
 
187
- // Filter by schema type
188
187
  if (selectedSchemaType !== 'all') {
189
188
  result = result.filter((msg) => msg.schemaExtension?.toLowerCase() === selectedSchemaType);
190
189
  }
191
190
 
192
- // Filter by search query
193
191
  if (searchQuery) {
194
192
  const query = searchQuery.toLowerCase();
195
193
  result = result.filter(
@@ -200,7 +198,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
200
198
  );
201
199
  }
202
200
 
203
- // Sort by name alphabetically
204
201
  result.sort((a, b) => {
205
202
  const nameA = a.data.name?.toLowerCase() || '';
206
203
  const nameB = b.data.name?.toLowerCase() || '';
@@ -210,7 +207,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
210
207
  return result;
211
208
  }, [latestMessages, searchQuery, selectedTypes, selectedSchemaType]);
212
209
 
213
- // Pagination
214
210
  const totalPages = Math.ceil(filteredMessages.length / ITEMS_PER_PAGE);
215
211
  const paginatedMessages = useMemo(() => {
216
212
  const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
@@ -221,7 +217,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
221
217
  setCurrentPage(1);
222
218
  }, [searchQuery, selectedTypes, selectedSchemaType]);
223
219
 
224
- // Load from query string on mount
225
220
  useEffect(() => {
226
221
  if (typeof window === 'undefined') return;
227
222
 
@@ -233,13 +228,11 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
233
228
  const specFilename = params.get('specFilename');
234
229
 
235
230
  if (id && version) {
236
- // Find the matching message
237
231
  const matchingMessage = schemas.find((msg) => {
238
232
  const idMatch = msg.data.id === id;
239
233
  const versionMatch = msg.data.version === version;
240
234
  const collectionMatch = !collection || msg.collection === collection;
241
235
 
242
- // For services, also match spec type and filename
243
236
  if (msg.collection === 'services') {
244
237
  const specTypeMatch = !specType || msg.specType === specType;
245
238
  const msgSpecFile = getSpecFile(msg);
@@ -254,7 +247,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
254
247
  setSelectedMessage(matchingMessage);
255
248
  setSelectedVersion(matchingMessage.data.version);
256
249
 
257
- // Scroll to the selected item after a brief delay to ensure DOM is ready
258
250
  setTimeout(() => {
259
251
  if (selectedItemRef.current) {
260
252
  selectedItemRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -264,7 +256,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
264
256
  }
265
257
  }, [schemas]);
266
258
 
267
- // Auto-select first message when filters change (only if no query params)
268
259
  useEffect(() => {
269
260
  if (typeof window === 'undefined') return;
270
261
 
@@ -278,24 +269,19 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
278
269
  }
279
270
  }, [filteredMessages, selectedMessage]);
280
271
 
281
- // Get the message to display (based on selected version)
282
272
  const displayMessage = useMemo(() => {
283
273
  if (!selectedMessage) return null;
284
274
 
285
275
  const groupKey = getGroupKey(selectedMessage);
286
-
287
276
  const versions = messagesByIdAndVersions.get(groupKey);
288
- if (!versions) return selectedMessage;
289
277
 
290
- // If no version selected, use the latest (which is the first in the sorted array)
278
+ if (!versions) return selectedMessage;
291
279
  if (!selectedVersion) return versions[0];
292
280
 
293
- // Find the message with the selected version
294
281
  const versionedMessage = versions.find((v) => v.data.version === selectedVersion);
295
282
  return versionedMessage || versions[0];
296
283
  }, [selectedMessage, selectedVersion, messagesByIdAndVersions]);
297
284
 
298
- // Save filter states to localStorage
299
285
  useEffect(() => {
300
286
  if (typeof window !== 'undefined') {
301
287
  localStorage.setItem('schemaRegistrySearchQuery', searchQuery);
@@ -314,7 +300,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
314
300
  }
315
301
  }, [selectedSchemaType]);
316
302
 
317
- // Keyboard shortcut for search (Cmd+K or Ctrl+K)
318
303
  useEffect(() => {
319
304
  const handleKeyDown = (e: KeyboardEvent) => {
320
305
  if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
@@ -322,11 +307,11 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
322
307
  searchInputRef.current?.focus();
323
308
  }
324
309
  };
310
+
325
311
  window.addEventListener('keydown', handleKeyDown);
326
312
  return () => window.removeEventListener('keydown', handleKeyDown);
327
313
  }, []);
328
314
 
329
- // Get available versions for the selected message
330
315
  const availableVersions = useMemo(() => {
331
316
  if (!displayMessage) return [];
332
317
  const groupKey = getGroupKey(displayMessage);
@@ -337,14 +322,12 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
337
322
 
338
323
  const handleVersionChange = (newVersion: string) => {
339
324
  setSelectedVersion(newVersion);
340
- // Update URL with new version
341
325
  const versionedMessage = availableVersions.find((v) => v.data.version === newVersion);
342
326
  if (versionedMessage) {
343
327
  updateUrlParams(versionedMessage);
344
328
  }
345
329
  };
346
330
 
347
- // Calculate stats
348
331
  const stats = useMemo(() => {
349
332
  return {
350
333
  total: latestMessages.length,
@@ -356,7 +339,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
356
339
  };
357
340
  }, [latestMessages]);
358
341
 
359
- // Toggle type selection (multi-select)
360
342
  const toggleType = (type: CollectionMessageTypes | 'specifications' | 'data-contracts') => {
361
343
  setSelectedTypes((prev) => {
362
344
  const next = new Set(prev);
@@ -369,26 +351,35 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
369
351
  });
370
352
  };
371
353
 
372
- // Clear all type filters
373
354
  const clearTypeFilters = () => {
374
355
  setSelectedTypes(new Set());
375
356
  };
376
357
 
377
- const hasActiveFilters = searchQuery || selectedTypes.size > 0 || selectedSchemaType !== 'all';
358
+ const hasActiveFilters = searchQuery.length > 0 || selectedTypes.size > 0 || selectedSchemaType !== 'all';
359
+ const activeFilterCount = (selectedTypes.size > 0 ? 1 : 0) + (selectedSchemaType !== 'all' ? 1 : 0);
360
+ const collectionTabs = [
361
+ { key: 'all', label: 'All', count: stats.total },
362
+ { key: 'events', label: 'Events', count: stats.events },
363
+ { key: 'commands', label: 'Commands', count: stats.commands },
364
+ { key: 'queries', label: 'Queries', count: stats.queries },
365
+ { key: 'specifications', label: 'Specs', count: stats.specifications },
366
+ { key: 'data-contracts', label: 'Contracts', count: stats.dataContracts },
367
+ ].filter((tab) => tab.count > 0);
378
368
 
379
369
  return (
380
- <div className="h-full flex flex-col overflow-hidden">
370
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
381
371
  <style
382
372
  dangerouslySetInnerHTML={{
383
373
  __html: '[data-theme="dark"] .schema-icon { filter: brightness(1.8) saturate(0.8); }',
384
374
  }}
385
375
  />
386
- <div className="flex-1 flex gap-0 overflow-hidden">
387
- {/* Left: Schema List */}
388
- <div className="w-[320px] flex-shrink-0 flex flex-col bg-[rgb(var(--ec-page-bg))] bg-gradient-to-bl from-[rgb(var(--ec-page-bg))] via-[rgb(var(--ec-page-bg))] to-[rgb(var(--ec-accent)/0.08)] border-r border-[rgb(var(--ec-page-border))] overflow-hidden">
389
- {/* Search */}
390
- <div className="flex-shrink-0 px-2 pt-2 pb-1.5">
391
- <div className="relative">
376
+ <div className="flex flex-1 min-h-0 gap-0 overflow-hidden">
377
+ <div
378
+ className="fixed top-0 z-20 flex h-screen flex-col overflow-hidden border-r border-[rgb(var(--ec-page-border))] bg-linear-to-b from-[rgb(var(--ec-page-bg))] via-[rgb(var(--ec-page-bg))] to-[rgb(var(--ec-accent)/0.06)]"
379
+ style={{ left: 'var(--ec-vertical-nav-width)', width: 'var(--ec-schema-sidebar-width, 360px)' }}
380
+ >
381
+ <div className="flex h-[60px] flex-shrink-0 items-center justify-between gap-2 border-b border-[rgb(var(--ec-page-border))] px-4">
382
+ <div className="relative min-w-0 flex-1">
392
383
  <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
393
384
  <MagnifyingGlassIcon className="h-4 w-4 text-[rgb(var(--ec-icon-color))]" />
394
385
  </div>
@@ -398,177 +389,144 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
398
389
  placeholder="Search schemas..."
399
390
  value={searchQuery}
400
391
  onChange={(e) => setSearchQuery(e.target.value)}
401
- className="w-full rounded-lg border border-[rgb(var(--ec-dropdown-border))] bg-[rgb(var(--ec-dropdown-bg))] py-2 pl-9 pr-8 text-sm text-[rgb(var(--ec-page-text))] placeholder:text-[rgb(var(--ec-icon-color))] focus:border-[rgb(var(--ec-accent))] focus:outline-hidden focus:ring-1 focus:ring-[rgb(var(--ec-accent)/0.3)] transition-all"
392
+ className="w-full rounded-lg border border-[rgb(var(--ec-dropdown-border))] bg-[rgb(var(--ec-dropdown-bg))] py-2 pl-9 pr-3 text-[12px] text-[rgb(var(--ec-page-text))] placeholder:text-[rgb(var(--ec-icon-color))] transition-all focus:border-[rgb(var(--ec-accent))] focus:outline-hidden focus:ring-1 focus:ring-[rgb(var(--ec-accent)/0.3)]"
402
393
  />
403
- {searchQuery ? (
404
- <button onClick={() => setSearchQuery('')} className="absolute inset-y-0 right-0 flex items-center pr-2.5">
405
- <XMarkIcon className="h-4 w-4 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))]" />
406
- </button>
407
- ) : (
408
- <div className="absolute inset-y-0 right-0 flex items-center pr-2.5 pointer-events-none">
409
- <kbd className="hidden sm:inline-flex items-center gap-0.5 rounded border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 text-[10px] font-medium text-[rgb(var(--ec-page-text-muted))]">
410
- <span className="text-[11px]">&#8984;</span>K
411
- </kbd>
412
- </div>
413
- )}
414
394
  </div>
395
+
396
+ {schemaTypes.length > 0 && (
397
+ <button
398
+ onClick={() => setShowFormatFilters((prev) => !prev)}
399
+ aria-pressed={showFormatFilters || selectedSchemaType !== 'all'}
400
+ className={`relative inline-flex h-9 w-9 items-center justify-center rounded-lg border transition-all ${
401
+ showFormatFilters || selectedSchemaType !== 'all'
402
+ ? 'border-[rgb(var(--ec-accent)/0.5)] bg-[rgb(var(--ec-accent))] text-white shadow-[0_10px_28px_rgb(var(--ec-accent)/0.3)]'
403
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-text-muted)/0.45)] hover:text-[rgb(var(--ec-page-text))]'
404
+ }`}
405
+ >
406
+ <AdjustmentsHorizontalIcon className="h-4.5 w-4.5" />
407
+ {activeFilterCount > 0 && (
408
+ <span className="absolute -right-1 -top-1 inline-flex h-5 min-w-5 items-center justify-center rounded-full border border-[rgb(var(--ec-page-bg))] bg-[rgb(var(--ec-page-text))] px-1 text-[10px] font-semibold tabular-nums text-[rgb(var(--ec-page-bg))]">
409
+ {activeFilterCount}
410
+ </span>
411
+ )}
412
+ </button>
413
+ )}
415
414
  </div>
416
415
 
417
- {/* Format Pills */}
418
- {schemaTypes.length > 0 && (
419
- <div className="flex-shrink-0 px-2.5 pb-2">
420
- <div className="flex items-center gap-1.5 flex-wrap">
421
- <button
422
- onClick={() => setSelectedSchemaType('all')}
423
- className={`px-2.5 py-1 rounded-md text-[11px] font-medium border transition-all ${
424
- selectedSchemaType === 'all'
425
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent)/0.08)]'
426
- : 'border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-text-muted))]'
427
- }`}
428
- >
429
- All formats
430
- </button>
431
- {schemaTypes.map((type) => {
432
- const labels: Record<string, string> = {
433
- json: 'JSON Schema',
434
- asyncapi: 'AsyncAPI',
435
- openapi: 'OpenAPI',
436
- graphql: 'GraphQL',
437
- avro: 'Avro',
438
- avsc: 'Avro',
439
- proto: 'Protobuf',
440
- yaml: 'YAML',
441
- yml: 'YAML',
442
- xml: 'XML',
443
- xsd: 'XML Schema',
444
- };
445
- return (
416
+ {(showFormatFilters || selectedSchemaType !== 'all' || selectedTypes.size > 0) && (
417
+ <div className="flex-shrink-0 border-b border-[rgb(var(--ec-page-border))] px-4 py-3">
418
+ <div className="rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-content-hover)/0.45)] p-3">
419
+ <div className="mb-3 flex items-center justify-between gap-3">
420
+ <span className="text-[11px] font-semibold uppercase tracking-[0.2em] text-[rgb(var(--ec-page-text-muted))]">
421
+ Filters
422
+ </span>
423
+ {hasActiveFilters && (
446
424
  <button
447
- key={type}
448
- onClick={() => setSelectedSchemaType(selectedSchemaType === type ? 'all' : type)}
449
- className={`px-2.5 py-1 rounded-md text-[11px] font-medium border transition-all ${
450
- selectedSchemaType === type
451
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent)/0.08)]'
452
- : 'border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-text-muted))]'
453
- }`}
425
+ onClick={() => {
426
+ setSearchQuery('');
427
+ clearTypeFilters();
428
+ setSelectedSchemaType('all');
429
+ }}
430
+ className="text-[11px] font-medium text-[rgb(var(--ec-accent))] hover:opacity-80"
454
431
  >
455
- {labels[type] || type.charAt(0).toUpperCase() + type.slice(1)}
432
+ Reset
456
433
  </button>
457
- );
458
- })}
434
+ )}
435
+ </div>
436
+
437
+ <div>
438
+ <div className="mb-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-[rgb(var(--ec-page-text-muted))]">
439
+ Type
440
+ </div>
441
+ <div className="flex flex-wrap gap-1.5">
442
+ {collectionTabs.map((tab) => {
443
+ const isAll = tab.key === 'all';
444
+ const isActive = isAll ? selectedTypes.size === 0 : selectedTypes.has(tab.key as CollectionMessageTypes);
445
+
446
+ return (
447
+ <button
448
+ key={tab.key}
449
+ onClick={() => {
450
+ if (isAll) {
451
+ clearTypeFilters();
452
+ return;
453
+ }
454
+ toggleType(tab.key as CollectionMessageTypes | 'specifications' | 'data-contracts');
455
+ }}
456
+ className={`inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] font-medium transition-all ${
457
+ isActive
458
+ ? 'border-[rgb(var(--ec-accent)/0.5)] bg-[rgb(var(--ec-accent)/0.16)] text-[rgb(var(--ec-page-text))] shadow-[0_0_0_1px_rgb(var(--ec-accent)/0.2)]'
459
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-text-muted)/0.3)] hover:text-[rgb(var(--ec-page-text))]'
460
+ }`}
461
+ >
462
+ <span>{tab.label}</span>
463
+ <span
464
+ className={`rounded-sm px-1 py-0.5 text-[8px] font-semibold tabular-nums ${
465
+ isActive
466
+ ? 'bg-[rgb(var(--ec-accent)/0.18)] text-[rgb(var(--ec-page-text))]'
467
+ : 'bg-[rgb(var(--ec-content-hover))] text-[rgb(var(--ec-page-text-muted))]'
468
+ }`}
469
+ >
470
+ {tab.count}
471
+ </span>
472
+ </button>
473
+ );
474
+ })}
475
+ </div>
476
+ </div>
477
+
478
+ {schemaTypes.length > 0 && (
479
+ <div className="mt-3">
480
+ <div className="mb-2 flex items-center justify-between gap-3">
481
+ <span className="text-[10px] font-semibold uppercase tracking-[0.18em] text-[rgb(var(--ec-page-text-muted))]">
482
+ Format
483
+ </span>
484
+ {selectedSchemaType !== 'all' && (
485
+ <button
486
+ onClick={() => setSelectedSchemaType('all')}
487
+ className="text-[10px] font-medium text-[rgb(var(--ec-accent))] hover:opacity-80"
488
+ >
489
+ Clear
490
+ </button>
491
+ )}
492
+ </div>
493
+ <div className="flex flex-wrap gap-1.5">
494
+ <button
495
+ onClick={() => setSelectedSchemaType('all')}
496
+ className={`rounded-md border px-2 py-1 text-[10px] font-medium transition-all ${
497
+ selectedSchemaType === 'all'
498
+ ? 'border-[rgb(var(--ec-accent)/0.45)] bg-[rgb(var(--ec-accent)/0.16)] text-[rgb(var(--ec-page-text))]'
499
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
500
+ }`}
501
+ >
502
+ All formats
503
+ </button>
504
+ {schemaTypes.map((type) => (
505
+ <button
506
+ key={type}
507
+ onClick={() => setSelectedSchemaType(selectedSchemaType === type ? 'all' : type)}
508
+ className={`rounded-md border px-2 py-1 text-[10px] font-medium transition-all ${
509
+ selectedSchemaType === type
510
+ ? 'border-[rgb(var(--ec-accent)/0.45)] bg-[rgb(var(--ec-accent)/0.16)] text-[rgb(var(--ec-page-text))]'
511
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
512
+ }`}
513
+ >
514
+ {SCHEMA_TYPE_LABELS[type] || type.charAt(0).toUpperCase() + type.slice(1)}
515
+ </button>
516
+ ))}
517
+ </div>
518
+ </div>
519
+ )}
459
520
  </div>
460
521
  </div>
461
522
  )}
462
523
 
463
- {/* Collection Type Tabs */}
464
- <div className="flex-shrink-0 px-2.5 border-b border-[rgb(var(--ec-page-border))]">
465
- <div className="flex items-center gap-3 overflow-x-auto">
466
- <button
467
- onClick={() => {
468
- clearTypeFilters();
469
- }}
470
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
471
- selectedTypes.size === 0
472
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
473
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
474
- }`}
475
- >
476
- All
477
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
478
- {stats.total}
479
- </span>
480
- </button>
481
- {stats.events > 0 && (
482
- <button
483
- onClick={() => toggleType('events')}
484
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
485
- selectedTypes.has('events')
486
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
487
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
488
- }`}
489
- >
490
- <BoltIcon className="h-3 w-3 text-orange-400" />
491
- Events
492
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
493
- {stats.events}
494
- </span>
495
- </button>
496
- )}
497
- {stats.commands > 0 && (
498
- <button
499
- onClick={() => toggleType('commands')}
500
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
501
- selectedTypes.has('commands')
502
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
503
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
504
- }`}
505
- >
506
- <ChatBubbleLeftIcon className="h-3 w-3 text-blue-400" />
507
- Commands
508
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
509
- {stats.commands}
510
- </span>
511
- </button>
512
- )}
513
- {stats.queries > 0 && (
514
- <button
515
- onClick={() => toggleType('queries')}
516
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
517
- selectedTypes.has('queries')
518
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
519
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
520
- }`}
521
- >
522
- <MagnifyingGlassSolidIcon className="h-3 w-3 text-green-400" />
523
- Queries
524
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
525
- {stats.queries}
526
- </span>
527
- </button>
528
- )}
529
- {stats.specifications > 0 && (
530
- <button
531
- onClick={() => toggleType('specifications')}
532
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
533
- selectedTypes.has('specifications')
534
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
535
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
536
- }`}
537
- >
538
- <CodeBracketIcon className="h-3 w-3 text-[rgb(var(--ec-accent)/0.7)]" />
539
- Specs
540
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
541
- {stats.specifications}
542
- </span>
543
- </button>
544
- )}
545
- {stats.dataContracts > 0 && (
546
- <button
547
- onClick={() => toggleType('data-contracts')}
548
- className={`inline-flex items-center gap-1.5 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${
549
- selectedTypes.has('data-contracts')
550
- ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
551
- : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
552
- }`}
553
- >
554
- <DocumentCheckIcon className="h-3 w-3 text-purple-400" />
555
- Contracts
556
- <span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-content-hover))] px-1.5 py-0.5 rounded-md">
557
- {stats.dataContracts}
558
- </span>
559
- </button>
560
- )}
561
- </div>
562
- </div>
563
-
564
- {/* Schema List */}
565
- <div className="flex-1 overflow-y-auto">
524
+ <div className="flex-1 overflow-y-auto px-3 py-3">
566
525
  {paginatedMessages.length > 0 ? (
567
- <div className="divide-y divide-[rgb(var(--ec-page-border)/0.5)]">
526
+ <div className="space-y-3">
568
527
  {paginatedMessages.map((message) => {
569
528
  const groupKey = getGroupKey(message);
570
529
  const isSelected = selectedGroupKey === groupKey;
571
-
572
530
  const versions = messagesByIdAndVersions.get(groupKey) || [message];
573
531
 
574
532
  return (
@@ -588,12 +546,12 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
588
546
  })}
589
547
  </div>
590
548
  ) : (
591
- <div className="flex flex-col items-center justify-center h-full p-8 text-center">
592
- <div className="flex items-center justify-center w-12 h-12 rounded-full bg-[rgb(var(--ec-content-hover))] mb-4">
549
+ <div className="flex h-full flex-col items-center justify-center rounded-[1.5rem] border border-dashed border-[rgb(var(--ec-page-border))] p-8 text-center">
550
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-[rgb(var(--ec-content-hover))]">
593
551
  <MagnifyingGlassIcon className="h-5 w-5 text-[rgb(var(--ec-icon-color))]" />
594
552
  </div>
595
- <h3 className="text-sm font-semibold text-[rgb(var(--ec-page-text))] mb-1">No schemas found</h3>
596
- <p className="text-sm text-[rgb(var(--ec-page-text-muted))] mb-4 max-w-[220px] leading-relaxed">
553
+ <h3 className="mb-1 text-sm font-semibold text-[rgb(var(--ec-page-text))]">No schemas found</h3>
554
+ <p className="mb-4 max-w-[220px] text-sm leading-relaxed text-[rgb(var(--ec-page-text-muted))]">
597
555
  {searchQuery ? `No results for "${searchQuery}"` : 'Try adjusting your filters'}
598
556
  </p>
599
557
  {hasActiveFilters && (
@@ -612,12 +570,13 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
612
570
  )}
613
571
  </div>
614
572
 
615
- {/* Pagination */}
616
573
  <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} />
617
574
  </div>
618
575
 
619
- {/* Right: Schema Details */}
620
- <div className="flex-1 bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] overflow-hidden">
576
+ <div
577
+ className="flex-1 min-h-0 min-w-0 overflow-hidden bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))]"
578
+ style={{ marginLeft: 'var(--ec-schema-sidebar-width, 360px)' }}
579
+ >
621
580
  {displayMessage ? (
622
581
  <SchemaDetailsPanel
623
582
  message={displayMessage}
@@ -627,13 +586,13 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
627
586
  apiAccessEnabled={apiAccessEnabled}
628
587
  />
629
588
  ) : (
630
- <div className="h-full flex items-center justify-center">
631
- <div className="text-center max-w-xs">
632
- <div className="flex items-center justify-center w-14 h-14 mx-auto mb-4 rounded-2xl bg-[rgb(var(--ec-content-hover))]">
589
+ <div className="flex h-full items-center justify-center">
590
+ <div className="max-w-xs text-center">
591
+ <div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[rgb(var(--ec-content-hover))]">
633
592
  <DocumentTextIcon className="h-7 w-7 text-[rgb(var(--ec-icon-color))]" />
634
593
  </div>
635
- <h3 className="text-base font-semibold text-[rgb(var(--ec-page-text))] mb-1">Select a schema</h3>
636
- <p className="text-sm text-[rgb(var(--ec-page-text-muted))] leading-relaxed">
594
+ <h3 className="mb-1 text-base font-semibold text-[rgb(var(--ec-page-text))]">Select a schema</h3>
595
+ <p className="text-sm leading-relaxed text-[rgb(var(--ec-page-text-muted))]">
637
596
  Choose a schema from the list to view details, compare versions, and access raw code
638
597
  </p>
639
598
  </div>