@eventcatalog/core 3.29.2 → 3.31.1

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