@eventcatalog/core 3.29.1 → 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 (82) 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-H5UC2A5F.js → chunk-6UG4JMUV.js} +1 -1
  6. package/dist/{chunk-4MSAPCV3.js → chunk-ATRBVTJ6.js} +1 -1
  7. package/dist/{chunk-PLNJC7NZ.js → chunk-K3ZVEX2Y.js} +13 -2
  8. package/dist/{chunk-V4OTI3PF.js → chunk-MVZKHUX2.js} +1 -1
  9. package/dist/{chunk-FVKDNLZK.js → chunk-RRBDF4MM.js} +1 -1
  10. package/dist/{chunk-24NGK43A.js → chunk-Z26P4PCB.js} +1 -1
  11. package/dist/constants.cjs +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/eventcatalog.cjs +14 -3
  14. package/dist/eventcatalog.js +6 -6
  15. package/dist/generate.cjs +1 -1
  16. package/dist/generate.js +3 -3
  17. package/dist/utils/cli-logger.cjs +1 -1
  18. package/dist/utils/cli-logger.js +2 -2
  19. package/dist/watcher.cjs +13 -2
  20. package/dist/watcher.js +1 -1
  21. package/eventcatalog/astro.config.mjs +1 -1
  22. package/eventcatalog/public/logo.png +0 -0
  23. package/eventcatalog/src/components/CopyAsMarkdown.tsx +2 -2
  24. package/eventcatalog/src/components/EnvironmentDropdown.tsx +33 -21
  25. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +3 -53
  26. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +144 -91
  27. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +112 -109
  28. package/eventcatalog/src/components/Header.astro +9 -19
  29. package/eventcatalog/src/components/MDX/Accordion/Accordion.tsx +12 -14
  30. package/eventcatalog/src/components/MDX/Accordion/AccordionGroup.astro +11 -3
  31. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +15 -5
  32. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +164 -53
  33. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +1 -1
  34. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +4 -4
  35. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +12 -10
  36. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +48 -77
  37. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +238 -169
  38. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +189 -230
  39. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +39 -36
  40. package/eventcatalog/src/components/Search/Search.astro +1 -1
  41. package/eventcatalog/src/components/Seo.astro +1 -1
  42. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +3 -3
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +229 -256
  44. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +78 -59
  45. package/eventcatalog/src/components/Tables/Discover/columns.tsx +130 -197
  46. package/eventcatalog/src/components/Tables/Table.tsx +21 -18
  47. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +79 -131
  48. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +104 -175
  49. package/eventcatalog/src/enterprise/auth/error.astro +1 -1
  50. package/eventcatalog/src/enterprise/auth/login.astro +1 -1
  51. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +95 -93
  52. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +174 -136
  53. package/eventcatalog/src/enterprise/fields/pages/fields.astro +10 -8
  54. package/eventcatalog/src/enterprise/integrations/eventcatalog-features.ts +0 -8
  55. package/eventcatalog/src/layouts/DirectoryLayout.astro +17 -88
  56. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +528 -146
  57. package/eventcatalog/src/layouts/VisualiserLayout.astro +7 -2
  58. package/eventcatalog/src/pages/_index.astro +5 -3
  59. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -3
  60. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +223 -73
  61. package/eventcatalog/src/pages/discover/[type]/index.astro +22 -141
  62. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +129 -29
  63. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +129 -29
  64. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +6 -2
  65. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +2 -2
  66. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +21 -18
  67. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +33 -32
  68. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +5 -1
  69. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +2 -2
  70. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +4 -6
  71. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +11 -4
  72. package/eventcatalog/src/pages/docs/users/[id]/index.astro +11 -4
  73. package/eventcatalog/src/pages/schemas/explorer/index.astro +10 -8
  74. package/eventcatalog/src/pages/studio.astro +1 -1
  75. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +2 -7
  76. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +2 -2
  77. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +2 -7
  78. package/eventcatalog/src/styles/theme.css +68 -12
  79. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +13 -0
  80. package/package.json +1 -1
  81. package/eventcatalog/public/logo.svg +0 -14
  82. package/eventcatalog/src/enterprise/plans/index.astro +0 -319
@@ -4,20 +4,32 @@ interface Props {
4
4
  sidebar?: boolean;
5
5
  description?: string;
6
6
  showNestedSideBar?: boolean;
7
+ showHeader?: boolean;
7
8
  }
8
9
 
9
10
  import {
10
11
  TableProperties,
11
12
  BotMessageSquare,
12
- Users,
13
13
  BookOpen,
14
- Sparkles,
15
- Rocket,
16
14
  FileText,
17
15
  SquareDashedMousePointerIcon,
18
- Braces,
16
+ FileCode,
19
17
  Waypoints,
18
+ PanelLeftClose,
19
+ Database,
20
+ UserRound,
21
+ UsersRound,
20
22
  } from 'lucide-react';
23
+ import {
24
+ RectangleGroupIcon,
25
+ GlobeAltIcon,
26
+ CubeIcon,
27
+ BoltIcon,
28
+ ChatBubbleLeftIcon,
29
+ MagnifyingGlassIcon,
30
+ QueueListIcon,
31
+ } from '@heroicons/react/24/outline';
32
+ import ServerIcon from '@heroicons/react/24/outline/ServerIcon';
21
33
  import BaseLayout from './BaseLayout.astro';
22
34
  import Header from '../components/Header.astro';
23
35
  import SideNav from '../components/SideNav/SideNav.astro';
@@ -52,24 +64,11 @@ import { buildUrl } from '@utils/url-builder';
52
64
  import { getQueries } from '@utils/collections/queries';
53
65
  import { hasLandingPageForDocs } from '@utils/pages';
54
66
 
55
- import {
56
- isEventCatalogUpgradeEnabled,
57
- isEmbedEnabled,
58
- isCustomStylesEnabled,
59
- isEventCatalogScaleEnabled,
60
- isSSR,
61
- } from '@utils/feature';
62
- import { getUsers } from '@utils/collections/users';
63
- import { getTeams } from '@utils/collections/teams';
67
+ import { isEmbedEnabled, isCustomStylesEnabled, isEventCatalogScaleEnabled, isCustomDocsEnabled, isSSR } from '@utils/feature';
64
68
 
65
69
  const catalogHasDefaultLandingPageForDocs = await hasLandingPageForDocs();
66
70
  const customDocs = await getCollection('customPages');
67
71
 
68
- // Get users and teams for directory navigation
69
- const users = await getUsers();
70
- const teams = await getTeams();
71
- const directoryDefaultUrl = users.length > 0 ? '/directory/users' : teams.length > 0 ? '/directory/teams' : '/directory/users';
72
-
73
72
  let events: any[] = [];
74
73
  let commands: any[] = [];
75
74
  let queries: any[] = [];
@@ -131,7 +130,7 @@ const userSideBarConfiguration = config.sidebar || [];
131
130
  const navigationItems = [
132
131
  {
133
132
  id: '/',
134
- label: 'Documentation',
133
+ label: 'Catalog',
135
134
  icon: BookOpen,
136
135
  href: buildUrl(config.landingPage || '/'),
137
136
  current:
@@ -144,16 +143,18 @@ const navigationItems = [
144
143
  !currentPath.includes('/schemas/fields')),
145
144
  },
146
145
  {
147
- id: '/discover',
148
- label: 'Explore',
149
- icon: TableProperties,
150
- href: buildUrl('/discover/domains'),
151
- current: currentPath.includes('/discover/'),
146
+ id: '/docs/custom',
147
+ label: 'Documentation',
148
+ icon: FileText,
149
+ href: getDefaultUrl('docs/custom', '/docs/custom'),
150
+ current: currentPath.includes('/docs/custom'),
151
+ isPremium: true,
152
+ visible: isCustomDocsEnabled(),
152
153
  },
153
154
  {
154
155
  id: '/schemas/explorer',
155
- label: 'Schema Explorer',
156
- icon: Braces,
156
+ label: 'Schemas',
157
+ icon: FileCode,
157
158
  href: buildUrl('/schemas/explorer'),
158
159
  current: currentPath.includes('/schemas/explorer') && !currentPath.includes('/schemas/fields'),
159
160
  },
@@ -161,23 +162,17 @@ const navigationItems = [
161
162
  ? [
162
163
  {
163
164
  id: '/schemas/fields',
164
- label: 'Schema Fields',
165
+ label: 'Schema Insights',
165
166
  icon: Waypoints,
166
167
  href: buildUrl('/schemas/fields'),
167
168
  current: currentPath.includes('/schemas/fields'),
168
169
  },
169
170
  ]
170
171
  : []),
171
- {
172
- id: '/directory',
173
- label: 'Users & Teams',
174
- icon: Users,
175
- href: buildUrl(directoryDefaultUrl),
176
- current: currentPath.includes('/directory'),
177
- },
178
172
  ].filter((item) => {
179
173
  const userSideBarOption = userSideBarConfiguration.find((config: { id: string; visible: boolean }) => config.id === item.id);
180
- return userSideBarOption ? userSideBarOption.visible : true;
174
+ const defaultVisible = item.visible ?? true;
175
+ return userSideBarOption ? userSideBarOption.visible : defaultVisible;
181
176
  });
182
177
 
183
178
  const studioNavigationItem = [
@@ -193,29 +188,115 @@ const studioNavigationItem = [
193
188
  return userSideBarOption ? userSideBarOption.visible : true;
194
189
  });
195
190
 
196
- const premiumFeatures = [
191
+ const premiumFeatures: Array<{
192
+ id: string;
193
+ label: string;
194
+ icon: any;
195
+ href: string;
196
+ current: boolean;
197
+ isPremium?: boolean;
198
+ }> = [];
199
+
200
+ const browseItems = [
197
201
  {
198
- id: '/docs/custom',
199
- label: 'Custom Documentation',
200
- icon: FileText,
201
- href: getDefaultUrl('docs/custom', '/docs/custom'),
202
- current: currentPath.includes('/docs/custom'),
203
- isPremium: true,
202
+ label: 'Domains',
203
+ icon: RectangleGroupIcon,
204
+ href: buildUrl('/discover/domains'),
205
+ current: currentPath === buildUrl('/discover/domains'),
204
206
  },
205
- ].filter((item) => {
206
- const userSideBarOption = userSideBarConfiguration.find((config: { id: string; visible: boolean }) => config.id === item.id);
207
- return userSideBarOption ? userSideBarOption.visible : true;
208
- });
207
+ {
208
+ label: 'Services',
209
+ icon: ServerIcon,
210
+ href: buildUrl('/discover/services'),
211
+ current: currentPath === buildUrl('/discover/services'),
212
+ },
213
+ {
214
+ label: 'External Systems',
215
+ icon: GlobeAltIcon,
216
+ href: buildUrl('/discover/external-systems'),
217
+ current: currentPath === buildUrl('/discover/external-systems'),
218
+ },
219
+ {
220
+ label: 'Events',
221
+ icon: BoltIcon,
222
+ href: buildUrl('/discover/events'),
223
+ current: currentPath === buildUrl('/discover/events'),
224
+ },
225
+ {
226
+ label: 'Commands',
227
+ icon: ChatBubbleLeftIcon,
228
+ href: buildUrl('/discover/commands'),
229
+ current: currentPath === buildUrl('/discover/commands'),
230
+ },
231
+ {
232
+ label: 'Queries',
233
+ icon: MagnifyingGlassIcon,
234
+ href: buildUrl('/discover/queries'),
235
+ current: currentPath === buildUrl('/discover/queries'),
236
+ },
237
+ {
238
+ label: 'Flows',
239
+ icon: QueueListIcon,
240
+ href: buildUrl('/discover/flows'),
241
+ current: currentPath === buildUrl('/discover/flows'),
242
+ },
243
+ {
244
+ label: 'Data Stores',
245
+ icon: Database,
246
+ href: buildUrl('/discover/containers'),
247
+ current: currentPath === buildUrl('/discover/containers'),
248
+ },
249
+ {
250
+ label: 'Data Products',
251
+ icon: CubeIcon,
252
+ href: buildUrl('/discover/data-products'),
253
+ current: currentPath === buildUrl('/discover/data-products'),
254
+ },
255
+ ];
256
+
257
+ const organizationItems = [
258
+ {
259
+ label: 'Teams',
260
+ icon: UsersRound,
261
+ href: buildUrl('/directory/teams'),
262
+ current: currentPath === buildUrl('/directory/teams'),
263
+ },
264
+ {
265
+ label: 'Users',
266
+ icon: UserRound,
267
+ href: buildUrl('/directory/users'),
268
+ current: currentPath === buildUrl('/directory/users'),
269
+ },
270
+ ];
209
271
 
210
272
  const currentNavigationItem = [...navigationItems, ...studioNavigationItem, ...premiumFeatures].find((item) => item.current);
211
- const { title, description, showNestedSideBar = true } = Astro.props;
273
+ const { title, description, showNestedSideBar = true, showHeader = true } = Astro.props;
212
274
 
213
275
  const canPageBeEmbedded = isEmbedEnabled();
214
276
  ---
215
277
 
216
278
  <BaseLayout title={`EventCatalog | ${title}`} description={description} ogTitle={title}>
217
279
  <Fragment slot="head">
280
+ <script is:inline>
281
+ (() => {
282
+ try {
283
+ const savedState = localStorage.getItem('eventcatalog-vertical-nav-collapsed');
284
+ const isCollapsed = savedState === null ? true : savedState === 'true';
285
+ document.documentElement.setAttribute('data-vertical-nav-collapsed', isCollapsed ? 'true' : 'false');
286
+ } catch (error) {
287
+ document.documentElement.setAttribute('data-vertical-nav-collapsed', 'true');
288
+ }
289
+ })();
290
+ </script>
218
291
  <style is:global>
292
+ :root {
293
+ --ec-vertical-nav-width: 14rem;
294
+ }
295
+
296
+ :root[data-vertical-nav-collapsed='true'] {
297
+ --ec-vertical-nav-width: 3rem;
298
+ }
299
+
219
300
  html,
220
301
  body {
221
302
  background-color: rgb(var(--ec-page-bg));
@@ -230,8 +311,110 @@ const canPageBeEmbedded = isEmbedEnabled();
230
311
  min-height: 100vh;
231
312
  min-height: 100dvh;
232
313
  background-color: rgb(var(--ec-page-bg));
314
+ --ec-sidebar-panel-width: 17rem;
315
+ display: flex;
316
+ flex-direction: column;
233
317
  }
234
318
 
319
+ .app-content-wrapper {
320
+ --ec-app-content-padding-left: 5rem;
321
+ --ec-app-content-padding-right: 5rem;
322
+ margin-left: auto;
323
+ margin-right: auto;
324
+ padding-left: var(--ec-app-content-padding-left);
325
+ padding-right: var(--ec-app-content-padding-right);
326
+ }
327
+ .app-shell {
328
+ min-height: calc(100vh - 4rem);
329
+ flex: 1;
330
+ }
331
+ .app-shell--rail-only {
332
+ margin-left: var(--ec-vertical-nav-width);
333
+ }
334
+ .app-shell--with-sidebar {
335
+ margin-left: calc(var(--ec-vertical-nav-width) + var(--ec-sidebar-panel-width));
336
+ }
337
+ .vertical-nav-rail {
338
+ width: var(--ec-vertical-nav-width);
339
+ border-right: 1px solid rgb(var(--ec-sidebar-border) / 1);
340
+ background: linear-gradient(
341
+ 180deg,
342
+ rgb(var(--ec-accent) / 0.1) 0%,
343
+ rgb(var(--ec-sidebar-bg-gradient)) 16%,
344
+ rgb(var(--ec-sidebar-bg)) 48%,
345
+ rgb(var(--ec-sidebar-bg)) 100%
346
+ );
347
+ box-shadow:
348
+ inset -1px 0 0 rgb(var(--ec-page-border) / 0.45),
349
+ 0 24px 44px -34px rgb(var(--ec-page-text) / 0.28);
350
+ overflow: hidden;
351
+ }
352
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-brand-text,
353
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-item-label,
354
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .rail-section-label,
355
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-count-pill,
356
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-label,
357
+ .vertical-nav-rail[data-collapsed='true'] .nav-brand-text,
358
+ .vertical-nav-rail[data-collapsed='true'] .nav-item-label,
359
+ .vertical-nav-rail[data-collapsed='true'] .rail-section-label,
360
+ .vertical-nav-rail[data-collapsed='true'] .nav-count-pill,
361
+ .vertical-nav-rail[data-collapsed='true'] .nav-label {
362
+ display: none;
363
+ }
364
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-brand-link,
365
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail [data-role='nav-item'],
366
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-secondary-item,
367
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail #vertical-nav-toggle,
368
+ .vertical-nav-rail[data-collapsed='true'] .nav-brand-link,
369
+ .vertical-nav-rail[data-collapsed='true'] [data-role='nav-item'],
370
+ .vertical-nav-rail[data-collapsed='true'] .nav-secondary-item,
371
+ .vertical-nav-rail[data-collapsed='true'] #vertical-nav-toggle {
372
+ justify-content: center;
373
+ padding-left: 0.25rem;
374
+ padding-right: 0.25rem;
375
+ }
376
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-brand-link,
377
+ .vertical-nav-rail[data-collapsed='true'] .nav-brand-link {
378
+ padding-left: 0;
379
+ padding-right: 0;
380
+ }
381
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail nav,
382
+ .vertical-nav-rail[data-collapsed='true'] nav {
383
+ padding-left: 0.25rem;
384
+ padding-right: 0.25rem;
385
+ }
386
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .rail-divider,
387
+ .vertical-nav-rail[data-collapsed='true'] .rail-divider {
388
+ margin-left: 0.25rem;
389
+ margin-right: 0.25rem;
390
+ }
391
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-premium-icon,
392
+ .vertical-nav-rail[data-collapsed='true'] .nav-premium-icon {
393
+ margin-left: 0;
394
+ }
395
+ :root[data-vertical-nav-collapsed='true'] .vertical-nav-rail .nav-collapse-icon,
396
+ .vertical-nav-rail[data-collapsed='true'] .nav-collapse-icon {
397
+ transform: rotate(180deg);
398
+ }
399
+ .sidebar-panel {
400
+ width: var(--ec-sidebar-panel-width);
401
+ min-width: var(--ec-sidebar-panel-width);
402
+ height: 100vh;
403
+ border-right: 1px solid rgb(var(--ec-content-border));
404
+ background: rgb(var(--ec-page-bg));
405
+ display: flex;
406
+ flex-direction: column;
407
+ position: fixed;
408
+ top: 0;
409
+ left: var(--ec-vertical-nav-width);
410
+ z-index: 20;
411
+ overflow-y: auto;
412
+ }
413
+ .content-panel {
414
+ min-width: 0;
415
+ flex: 1;
416
+ min-height: calc(100vh - 4rem);
417
+ }
235
418
  .sidebar-transition {
236
419
  transition-property: width, transform;
237
420
  transition-duration: 300ms;
@@ -283,119 +466,154 @@ const canPageBeEmbedded = isEmbedEnabled();
283
466
  {/* Load search data even when sidebar is hidden */}
284
467
  <SearchDataLoader />
285
468
  <main id="eventcatalog-application" class="relative">
286
- <div transition:persist="site-header">
287
- <Header />
288
- </div>
289
- <div class="flex">
469
+ {
470
+ showHeader && (
471
+ <div transition:persist="site-header">
472
+ <Header showNestedSideBar={showNestedSideBar} />
473
+ </div>
474
+ )
475
+ }
476
+ <div class={`app-shell ${showNestedSideBar ? 'app-shell--with-sidebar' : 'app-shell--rail-only'}`}>
290
477
  <aside class="flex" id="eventcatalog-vertical-nav">
291
478
  <div
292
- class="fixed flex flex-col items-center w-14 h-screen py-3 bg-[rgb(var(--ec-sidebar-bg))] bg-gradient-to-t from-[rgb(var(--ec-accent)/0.08)] to-[rgb(var(--ec-sidebar-bg))] border-r border-[rgb(var(--ec-sidebar-border))] z-20 shadow-md justify-between"
479
+ id="vertical-nav-rail"
480
+ data-collapsed="true"
481
+ transition:persist="vertical-nav-rail"
482
+ class="vertical-nav-rail fixed top-0 left-0 flex flex-col h-screen z-30 transition-[width] duration-200"
293
483
  >
294
- <nav class="flex flex-col h-[calc(100vh-70px)] justify-between">
295
- <div class="flex flex-col items-center flex-1 space-y-6">
296
- {
297
- navigationItems.map((item) => {
298
- return (
299
- <a
300
- id={item.id}
301
- data-role="nav-item"
302
- href={item.href}
303
- aria-label={item.label}
304
- class={`p-1.5 inline-block transition-colors duration-200 rounded-lg ${
305
- item.current
306
- ? 'text-[rgb(var(--ec-sidebar-active-text))] bg-[rgb(var(--ec-sidebar-active-bg))]'
307
- : 'hover:bg-[rgb(var(--ec-sidebar-hover-bg))] hover:text-[rgb(var(--ec-sidebar-active-text))] text-[rgb(var(--ec-sidebar-text))]'
308
- }`}
309
- >
310
- <div class="has-tooltip">
311
- <span
312
- class="tooltip rounded shadow-lg p-1 text-xs bg-gray-900 text-white ml-10 whitespace-nowrap"
313
- aria-hidden="true"
314
- >
315
- {item.label}
316
- </span>
317
- <item.icon className="h-6 w-6" aria-hidden="true" />
318
- </div>
319
- </a>
320
- );
321
- })
322
- }
323
-
324
- <hr class="w-8 border-t border-[rgb(var(--ec-sidebar-border))]" />
325
-
326
- {
327
- premiumFeatures.map((item) => (
484
+ <a
485
+ href={buildUrl(config.landingPage || '/')}
486
+ aria-label="Home"
487
+ class="nav-brand-link flex items-center gap-2.5 px-4 h-[3.75rem] border-b border-[rgb(var(--ec-sidebar-border)/0.7)] flex-shrink-0"
488
+ >
489
+ <img
490
+ src={config?.logo?.src ? buildUrl('/' + config.logo.src.replace(/^\/+/, ''), true) : buildUrl('/logo.png', true)}
491
+ alt={config?.logo?.alt || 'EventCatalog'}
492
+ class="w-6 h-6 flex-shrink-0"
493
+ />
494
+ <span class="nav-brand-text text-[15px] font-semibold text-[rgb(var(--ec-page-text))] tracking-tight">
495
+ {config?.logo?.text || config?.organizationName || 'EventCatalog'}
496
+ </span>
497
+ </a>
498
+
499
+ <nav class="flex flex-col flex-1 overflow-y-auto px-3 py-4 gap-1">
500
+ {
501
+ navigationItems.map((item) => {
502
+ return (
328
503
  <a
329
504
  id={item.id}
330
505
  data-role="nav-item"
331
506
  href={item.href}
332
507
  aria-label={item.label}
333
- class={`p-1.5 inline-block transition-colors duration-200 rounded-lg mb-8 relative ${
508
+ class={`flex items-center gap-3 px-3 py-2.5 rounded-xl border text-[13px] font-medium transition-all duration-150 ${
334
509
  item.current
335
- ? 'text-[rgb(var(--ec-sidebar-active-text))] bg-[rgb(var(--ec-sidebar-active-bg))]'
336
- : 'hover:bg-[rgb(var(--ec-sidebar-hover-bg))] hover:text-[rgb(var(--ec-sidebar-active-text))] text-[rgb(var(--ec-sidebar-text))]'
510
+ ? 'border-[rgb(var(--ec-accent)/0.2)] bg-[rgb(var(--ec-page-bg)/0.88)] text-[rgb(var(--ec-accent-text))] shadow-sm'
511
+ : 'border-transparent text-[rgb(var(--ec-sidebar-text))] hover:border-[rgb(var(--ec-sidebar-border)/0.65)] hover:bg-[rgb(var(--ec-page-bg)/0.78)] hover:text-[rgb(var(--ec-page-text))]'
337
512
  }`}
513
+ title={item.label}
338
514
  >
339
- <div class="has-tooltip">
340
- <span
341
- class="tooltip rounded shadow-lg p-1 text-xs bg-gray-900 text-white ml-10 flex items-center gap-1 whitespace-nowrap"
342
- aria-hidden="true"
343
- >
344
- <Sparkles className="h-3 w-3" aria-hidden="true" /> {item.label}
345
- </span>
346
- <item.icon className="h-6 w-6" aria-hidden="true" />
347
- <div
348
- class="absolute -top-1 -right-1 bg-gradient-to-r from-amber-400 to-amber-500 rounded-full p-0.5 shadow-lg"
349
- aria-hidden="true"
350
- >
351
- <Sparkles className="h-2 w-2 text-white" aria-hidden="true" />
352
- </div>
353
- </div>
515
+ <item.icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
516
+ <span class="nav-item-label">{item.label}</span>
354
517
  </a>
355
- ))
356
- }
518
+ );
519
+ })
520
+ }
521
+
522
+ <hr class="rail-divider my-3 border-t border-[rgb(var(--ec-sidebar-border)/0.7)]" />
523
+
524
+ <div
525
+ class="rail-section-label px-3 pb-1 text-[0.65rem] font-semibold tracking-[0.18em] uppercase text-[rgb(var(--ec-sidebar-text))]"
526
+ >
527
+ Browse
357
528
  </div>
358
529
 
359
530
  {
360
- isEventCatalogUpgradeEnabled() && (
361
- <div class="mb-4">
362
- <a
363
- id="/pro"
364
- data-role="nav-item"
365
- href={buildUrl('/plans')}
366
- aria-label="Upgrade EventCatalog"
367
- class={`p-1.5 inline-block transition-colors duration-200 rounded-lg ${currentPath.includes('/pro') ? 'text-[rgb(var(--ec-sidebar-active-text))] bg-[rgb(var(--ec-sidebar-active-bg))]' : 'bg-[rgb(var(--ec-sidebar-bg-gradient))] hover:bg-[rgb(var(--ec-sidebar-hover-bg))] hover:text-[rgb(var(--ec-sidebar-active-text))] text-[rgb(var(--ec-sidebar-text))]'}`}
368
- >
369
- <div class="has-tooltip">
370
- <span
371
- class="tooltip rounded shadow-lg p-1 text-xs bg-gray-900 text-white ml-10 whitespace-nowrap"
372
- aria-hidden="true"
373
- >
374
- Upgrade EventCatalog
375
- </span>
376
- <Rocket className="h-6 w-6" aria-hidden="true" />
377
- </div>
378
- </a>
379
- </div>
380
- )
531
+ browseItems.map((item) => (
532
+ <a
533
+ href={item.href}
534
+ data-role="secondary-nav-item"
535
+ aria-label={item.label}
536
+ class={`nav-secondary-item flex items-center gap-3 px-3 py-2.5 rounded-xl border text-[13px] font-medium transition-all duration-150 ${
537
+ item.current
538
+ ? 'border-[rgb(var(--ec-accent)/0.2)] bg-[rgb(var(--ec-page-bg)/0.88)] text-[rgb(var(--ec-accent-text))] shadow-sm'
539
+ : 'border-transparent text-[rgb(var(--ec-sidebar-text))] hover:border-[rgb(var(--ec-sidebar-border)/0.65)] hover:bg-[rgb(var(--ec-page-bg)/0.78)] hover:text-[rgb(var(--ec-page-text))]'
540
+ }`}
541
+ title={item.label}
542
+ >
543
+ <item.icon className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" />
544
+ <span class="nav-item-label">{item.label}</span>
545
+ </a>
546
+ ))
547
+ }
548
+
549
+ <hr class="rail-divider my-3 border-t border-[rgb(var(--ec-sidebar-border)/0.7)]" />
550
+
551
+ <div
552
+ class="rail-section-label px-3 pb-1 text-[0.65rem] font-semibold tracking-[0.18em] uppercase text-[rgb(var(--ec-sidebar-text))]"
553
+ >
554
+ Organization
555
+ </div>
556
+
557
+ {
558
+ organizationItems.map((item) => (
559
+ <a
560
+ href={item.href}
561
+ data-role="secondary-nav-item"
562
+ aria-label={item.label}
563
+ class={`nav-secondary-item flex items-center gap-3 px-3 py-2.5 rounded-xl border text-[13px] font-medium transition-all duration-150 ${
564
+ item.current
565
+ ? 'border-[rgb(var(--ec-accent)/0.2)] bg-[rgb(var(--ec-page-bg)/0.88)] text-[rgb(var(--ec-accent-text))] shadow-sm'
566
+ : 'border-transparent text-[rgb(var(--ec-sidebar-text))] hover:border-[rgb(var(--ec-sidebar-border)/0.65)] hover:bg-[rgb(var(--ec-page-bg)/0.78)] hover:text-[rgb(var(--ec-page-text))]'
567
+ }`}
568
+ title={item.label}
569
+ >
570
+ <item.icon className="h-3.5 w-3.5 flex-shrink-0" aria-hidden="true" />
571
+ <span class="nav-item-label">{item.label}</span>
572
+ </a>
573
+ ))
574
+ }
575
+
576
+ {premiumFeatures.length > 0 && <hr class="rail-divider my-2 border-t border-[rgb(var(--ec-sidebar-border)/0.7)]" />}
577
+
578
+ {
579
+ premiumFeatures.map((item) => (
580
+ <a
581
+ id={item.id}
582
+ data-role="nav-item"
583
+ href={item.href}
584
+ aria-label={item.label}
585
+ class={`flex items-center gap-3 px-3 py-2.5 rounded-xl border text-[13px] font-medium transition-all duration-150 ${
586
+ item.current
587
+ ? 'border-[rgb(var(--ec-accent)/0.2)] bg-[rgb(var(--ec-page-bg)/0.88)] text-[rgb(var(--ec-accent-text))] shadow-sm'
588
+ : 'border-transparent text-[rgb(var(--ec-sidebar-text))] hover:border-[rgb(var(--ec-sidebar-border)/0.65)] hover:bg-[rgb(var(--ec-page-bg)/0.78)] hover:text-[rgb(var(--ec-page-text))]'
589
+ }`}
590
+ title={item.label}
591
+ >
592
+ <item.icon className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
593
+ <span class="nav-item-label">{item.label}</span>
594
+ </a>
595
+ ))
381
596
  }
382
597
  </nav>
383
- </div>
384
598
 
385
- {
386
- showNestedSideBar && (
387
- <SideNav
388
- id="sidebar"
389
- class={`sidebar-transition h-content bg-[rgb(var(--ec-sidebar-bg))] bg-gradient-to-bl from-[rgb(var(--ec-sidebar-bg))] via-[rgb(var(--ec-sidebar-bg))] to-[rgb(var(--ec-accent)/0.08)] border-r border-[rgb(var(--ec-sidebar-border))] w-[320px] ml-14`}
390
- />
391
- )
392
- }
599
+ <div class="border-t border-[rgb(var(--ec-sidebar-border)/0.7)] px-3 py-3 flex-shrink-0">
600
+ <button
601
+ id="vertical-nav-toggle"
602
+ type="button"
603
+ aria-label="Collapse sidebar"
604
+ class="flex items-center gap-3 px-3 py-2.5 rounded-xl border border-transparent text-[13px] font-medium text-[rgb(var(--ec-sidebar-text))] hover:border-[rgb(var(--ec-sidebar-border)/0.65)] hover:bg-[rgb(var(--ec-page-bg)/0.78)] hover:text-[rgb(var(--ec-page-text))] transition-all duration-150 w-full"
605
+ >
606
+ <PanelLeftClose className="h-4 w-4 flex-shrink-0 nav-collapse-icon" aria-hidden="true" />
607
+ <span class="nav-label">Collapse</span>
608
+ </button>
609
+ </div>
610
+ </div>
393
611
  </aside>
394
- <main
395
- class={`sidebar-transition w-full max-h-content overflow-y-auto bg-[rgb(var(--ec-page-bg))] ${showNestedSideBar ? 'ml-0' : 'ml-14'}`}
396
- id="content"
397
- >
398
- <slot />
612
+ {showNestedSideBar && <SideNav id="sidebar" class={`sidebar-panel sidebar-transition`} />}
613
+ <main class="content-panel sidebar-transition w-full bg-[rgb(var(--ec-page-bg))]" id="content">
614
+ <div class="app-content-wrapper">
615
+ <slot />
616
+ </div>
399
617
  </main>
400
618
 
401
619
  <!-- Create a overlay that tells people to purchase backstage plugin if they want to embed the page -->
@@ -418,14 +636,126 @@ const canPageBeEmbedded = isEmbedEnabled();
418
636
  </BaseLayout>
419
637
 
420
638
  <ClientRouter />
421
- <script define:vars={{ navigationItems, currentNavigationItem, canPageBeEmbedded }}>
639
+ <script
640
+ define:vars={{
641
+ navigationItems,
642
+ currentNavigationItem,
643
+ canPageBeEmbedded,
644
+ }}
645
+ >
646
+ const VERTICAL_NAV_STORAGE_KEY = 'eventcatalog-vertical-nav-collapsed';
647
+ const ACTIVE_NAV_CLASSES = [
648
+ 'border-[rgb(var(--ec-accent)/0.2)]',
649
+ 'bg-[rgb(var(--ec-page-bg)/0.88)]',
650
+ 'text-[rgb(var(--ec-accent-text))]',
651
+ 'shadow-sm',
652
+ ];
653
+ const INACTIVE_NAV_CLASSES = [
654
+ 'border-transparent',
655
+ 'text-[rgb(var(--ec-sidebar-text))]',
656
+ 'hover:border-[rgb(var(--ec-sidebar-border)/0.65)]',
657
+ 'hover:bg-[rgb(var(--ec-page-bg)/0.78)]',
658
+ 'hover:text-[rgb(var(--ec-page-text))]',
659
+ ];
660
+
661
+ const isNavItemCurrent = (id, pathname) => {
662
+ if (id === '/') {
663
+ return (
664
+ pathname === '/' ||
665
+ (pathname.includes('/docs') && !pathname.includes('/docs/custom')) ||
666
+ pathname.includes('/architecture/') ||
667
+ pathname.includes('/visualiser') ||
668
+ (pathname.includes('/schemas') && !pathname.includes('/schemas/explorer') && !pathname.includes('/schemas/fields'))
669
+ );
670
+ }
671
+
672
+ if (id === '/schemas/explorer') {
673
+ return pathname.includes('/schemas/explorer') && !pathname.includes('/schemas/fields');
674
+ }
675
+
676
+ if (id === '/schemas/fields') {
677
+ return pathname.includes('/schemas/fields');
678
+ }
679
+
680
+ if (id === '/studio') {
681
+ return pathname.includes('/studio');
682
+ }
683
+
684
+ if (id === '/docs/custom') {
685
+ return pathname.includes('/docs/custom');
686
+ }
687
+
688
+ return false;
689
+ };
690
+
691
+ const syncNavItemStates = () => {
692
+ const pathname = window.location.pathname;
693
+ const navItems = document.querySelectorAll('[data-role="nav-item"]');
694
+ const secondaryNavItems = document.querySelectorAll('[data-role="secondary-nav-item"]');
695
+
696
+ navItems.forEach((item) => {
697
+ const id = item.getAttribute('id');
698
+ const isCurrent = id ? isNavItemCurrent(id, pathname) : false;
699
+
700
+ if (isCurrent) {
701
+ item.classList.remove(...INACTIVE_NAV_CLASSES);
702
+ item.classList.add(...ACTIVE_NAV_CLASSES);
703
+ } else {
704
+ item.classList.remove(...ACTIVE_NAV_CLASSES);
705
+ item.classList.add(...INACTIVE_NAV_CLASSES);
706
+ }
707
+ });
708
+
709
+ secondaryNavItems.forEach((item) => {
710
+ const href = item.getAttribute('href');
711
+ const targetPath = href ? new URL(href, window.location.origin).pathname : null;
712
+ const isCurrent = targetPath ? pathname === targetPath : false;
713
+
714
+ if (isCurrent) {
715
+ item.classList.remove(...INACTIVE_NAV_CLASSES);
716
+ item.classList.add(...ACTIVE_NAV_CLASSES);
717
+ } else {
718
+ item.classList.remove(...ACTIVE_NAV_CLASSES);
719
+ item.classList.add(...INACTIVE_NAV_CLASSES);
720
+ }
721
+ });
722
+ };
723
+
724
+ const getPersistedVerticalNavCollapsedState = () => {
725
+ try {
726
+ const savedState = localStorage.getItem(VERTICAL_NAV_STORAGE_KEY);
727
+ return savedState === null ? true : savedState === 'true';
728
+ } catch (error) {
729
+ return true;
730
+ }
731
+ };
732
+
733
+ const applyPersistedVerticalNavState = () => {
734
+ const isCollapsed = getPersistedVerticalNavCollapsedState();
735
+ document.documentElement.setAttribute('data-vertical-nav-collapsed', isCollapsed ? 'true' : 'false');
736
+
737
+ const verticalNavRail = document.getElementById('vertical-nav-rail');
738
+ if (verticalNavRail) {
739
+ verticalNavRail.setAttribute('data-collapsed', isCollapsed ? 'true' : 'false');
740
+ }
741
+ };
742
+
743
+ // Apply immediately when this script executes so transitions don't briefly re-open the rail.
744
+ applyPersistedVerticalNavState();
745
+
746
+ // Re-apply around Astro view transitions because the incoming document can momentarily drop the root attribute.
747
+ document.addEventListener('astro:before-preparation', applyPersistedVerticalNavState);
748
+ document.addEventListener('astro:after-swap', applyPersistedVerticalNavState);
749
+
422
750
  // Listen for Astro transititions
423
751
  document.addEventListener('astro:page-load', () => {
752
+ applyPersistedVerticalNavState();
424
753
  document.dispatchEvent(new CustomEvent('contentLoaded'));
425
754
  });
426
755
 
427
756
  // Listen for DOM loaded
428
757
  document.addEventListener('DOMContentLoaded', () => {
758
+ applyPersistedVerticalNavState();
429
759
  document.dispatchEvent(new CustomEvent('contentLoaded'));
430
760
  });
431
761
 
@@ -435,6 +765,36 @@ const canPageBeEmbedded = isEmbedEnabled();
435
765
  const params = Object.fromEntries(urlSearchParams.entries());
436
766
  const embeded = params.embed === 'true' ? true : false;
437
767
  const content = document.getElementById('content');
768
+ const shell = document.querySelector('.app-shell');
769
+ const app = document.getElementById('eventcatalog-application');
770
+ const verticalNavRail = document.getElementById('vertical-nav-rail');
771
+ const verticalNavToggle = document.getElementById('vertical-nav-toggle');
772
+ const navToggleLabel = verticalNavToggle?.querySelector('.nav-label');
773
+ const collapseIcon = verticalNavToggle?.querySelector('.nav-collapse-icon');
774
+
775
+ const setVerticalNavCollapsedState = (collapsed) => {
776
+ if (!app || !verticalNavRail || !verticalNavToggle || !navToggleLabel) return;
777
+ const railTextElements = verticalNavRail.querySelectorAll(
778
+ '.nav-brand-text, .nav-item-label, .rail-section-label, .nav-count-pill, .nav-label'
779
+ );
780
+
781
+ document.documentElement.setAttribute('data-vertical-nav-collapsed', collapsed ? 'true' : 'false');
782
+ verticalNavRail.setAttribute('data-collapsed', collapsed ? 'true' : 'false');
783
+ verticalNavToggle.setAttribute('aria-label', collapsed ? 'Expand sidebar' : 'Collapse sidebar');
784
+ verticalNavToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
785
+ navToggleLabel.textContent = collapsed ? 'Expand' : 'Collapse';
786
+ railTextElements.forEach((element) => {
787
+ if (collapsed) {
788
+ element.setAttribute('hidden', '');
789
+ } else {
790
+ element.removeAttribute('hidden');
791
+ }
792
+ });
793
+
794
+ if (collapseIcon) {
795
+ collapseIcon.setAttribute('title', collapsed ? 'Expand sidebar' : 'Collapse sidebar');
796
+ }
797
+ };
438
798
 
439
799
  if (embeded && !canPageBeEmbedded) {
440
800
  const overlay = document.getElementById('embed-overlay');
@@ -458,23 +818,45 @@ const canPageBeEmbedded = isEmbedEnabled();
458
818
  if (element) element.style.display = 'none';
459
819
  });
460
820
 
461
- content.classList.remove('ml-14');
462
- content.classList.remove('max-h-content');
821
+ document.body.style.overflow = '';
822
+ app.style.overflow = '';
823
+ if (shell) shell.style.marginLeft = '0';
463
824
  return;
464
825
  }
465
826
 
827
+ if (shell) {
828
+ shell.style.marginLeft = '';
829
+ }
830
+
831
+ setVerticalNavCollapsedState(getPersistedVerticalNavCollapsedState());
832
+
833
+ syncNavItemStates();
834
+
466
835
  const navItems = document.querySelectorAll('[data-role="nav-item"]');
467
836
 
468
837
  // Navigation items simply navigate to their href - no toggle logic
469
838
  navItems.forEach((item) => {
470
- item.addEventListener('click', (e) => {
839
+ item.onclick = (e) => {
471
840
  const id = item.getAttribute('id');
472
841
  const navItem = navigationItems.find((navItem) => navItem.id === id);
473
842
 
474
843
  if (navItem && navItem.href) {
475
844
  window.location.href = navItem.href;
476
845
  }
477
- });
846
+ };
478
847
  });
848
+
849
+ if (verticalNavToggle) {
850
+ verticalNavToggle.onclick = () => {
851
+ const isCollapsed = verticalNavRail?.getAttribute('data-collapsed') === 'true';
852
+ const nextState = !isCollapsed;
853
+
854
+ setVerticalNavCollapsedState(nextState);
855
+
856
+ try {
857
+ localStorage.setItem(VERTICAL_NAV_STORAGE_KEY, String(nextState));
858
+ } catch (error) {}
859
+ };
860
+ }
479
861
  });
480
862
  </script>