@eventcatalog/core 2.47.1 → 2.48.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 (28) 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/catalog-to-astro-content-directory.cjs +16 -1
  6. package/dist/catalog-to-astro-content-directory.js +2 -2
  7. package/dist/{chunk-SFA7F3CQ.js → chunk-IZMM7ZGY.js} +8 -1
  8. package/dist/{chunk-EXAALOQA.js → chunk-LDBRNJIL.js} +9 -1
  9. package/dist/{chunk-QWDFTW7H.js → chunk-M35UFAGG.js} +1 -1
  10. package/dist/{chunk-WWYOMQLW.js → chunk-WAVFA46U.js} +1 -1
  11. package/dist/{chunk-DCLTVJDP.js → chunk-XE6PFSH5.js} +2 -2
  12. package/dist/{chunk-YJYT2E6S.js → chunk-ZI5ZP7I2.js} +1 -1
  13. package/dist/constants.cjs +1 -1
  14. package/dist/constants.js +1 -1
  15. package/dist/eventcatalog.cjs +54 -40
  16. package/dist/eventcatalog.js +6 -6
  17. package/dist/map-catalog-to-astro.cjs +9 -1
  18. package/dist/map-catalog-to-astro.js +1 -1
  19. package/dist/watcher.cjs +14 -17
  20. package/dist/watcher.js +2 -3
  21. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +1 -0
  22. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +2 -0
  23. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +310 -106
  24. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +86 -7
  25. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -0
  26. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +0 -2
  27. package/eventcatalog/tsconfig.json +1 -0
  28. package/package.json +1 -1
@@ -54,6 +54,7 @@ const MessageList: React.FC<MessageListProps> = ({ messages, decodedCurrentPath,
54
54
  <li key={message.id} data-active={decodedCurrentPath === message.href}>
55
55
  <a
56
56
  href={message.href}
57
+ data-active={decodedCurrentPath === message.href}
57
58
  className={`flex items-center justify-between px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
58
59
  decodedCurrentPath.includes(message.href) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
59
60
  }`}
@@ -26,6 +26,7 @@ const SpecificationList: React.FC<SpecificationListProps> = ({ specifications, i
26
26
  <a
27
27
  key={`${spec.name}-asyncapi`}
28
28
  href={buildUrl(`/docs/services/${id}/${version}/asyncapi/${spec.filenameWithoutExtension}`)}
29
+ data-active={window.location.href.includes(`docs/services/${id}/${version}/asyncapi`)}
29
30
  className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md justify-between ${
30
31
  window.location.href.includes(`docs/services/${id}/${version}/asyncapi`) ? 'bg-purple-100' : 'hover:bg-purple-100'
31
32
  }`}
@@ -42,6 +43,7 @@ const SpecificationList: React.FC<SpecificationListProps> = ({ specifications, i
42
43
  <a
43
44
  key={`${spec.name}-openapi`}
44
45
  href={buildUrl(`/docs/services/${id}/${version}/spec/${spec.filenameWithoutExtension}`)}
46
+ data-active={window.location.href.includes(`docs/services/${id}/${version}/spec/${spec.filenameWithoutExtension}`)}
45
47
  className={`items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md flex justify-between ${
46
48
  window.location.href.includes(`docs/services/${id}/${version}/spec/${spec.filenameWithoutExtension}`)
47
49
  ? 'bg-purple-100'
@@ -1,10 +1,11 @@
1
1
  import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import { ChevronDownIcon } from '@heroicons/react/24/outline';
2
+ import { ChevronDownIcon, ChevronDoubleDownIcon, ChevronDoubleUpIcon, XMarkIcon } from '@heroicons/react/24/outline';
3
3
  import { buildUrl, buildUrlWithParams } from '@utils/url-builder';
4
4
  import CollapsibleGroup from './components/CollapsibleGroup';
5
5
  import MessageList from './components/MessageList';
6
6
  import SpecificationsList from './components/SpecificationList';
7
7
  import type { MessageItem, ServiceItem, ListViewSideBarProps, DomainItem, FlowItem, Resources } from './types';
8
+ import { PanelLeft } from 'lucide-react';
8
9
  const STORAGE_KEY = 'EventCatalog:catalogSidebarCollapsedGroups';
9
10
  const DEBOUNCE_DELAY = 300; // 300ms debounce delay
10
11
 
@@ -98,6 +99,7 @@ const ServiceItem = React.memo(
98
99
  <div className="space-y-0.5 border-gray-200/80 border-l pl-3 ml-[9px] mt-1">
99
100
  <a
100
101
  href={`${item.href}`}
102
+ data-active={decodedCurrentPath === item.href}
101
103
  className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
102
104
  decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
103
105
  }`}
@@ -110,6 +112,7 @@ const ServiceItem = React.memo(
110
112
  serviceName: item.name,
111
113
  serviceId: item.id,
112
114
  })}
115
+ data-active={window.location.href.includes(`serviceId=${item.id}`)}
113
116
  className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
114
117
  window.location.href.includes(`serviceId=${item.id}`) ? 'bg-purple-100' : 'hover:bg-purple-100'
115
118
  }`}
@@ -204,6 +207,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
204
207
  const [searchTerm, setSearchTerm] = useState('');
205
208
  const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
206
209
  const [isInitialized, setIsInitialized] = useState(false);
210
+ const [isExpanded, setIsExpanded] = useState(true);
207
211
  const [collapsedGroups, setCollapsedGroups] = useState<{ [key: string]: boolean }>(() => {
208
212
  if (typeof window !== 'undefined') {
209
213
  const saved = window.localStorage.getItem(STORAGE_KEY);
@@ -241,9 +245,55 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
241
245
  );
242
246
  };
243
247
 
248
+ // Enhanced domain filtering that considers parent-subdomain relationships
249
+ const filterDomains = (domains: any[]) => {
250
+ const filteredDomains: any[] = [];
251
+
252
+ domains.forEach((domain: any) => {
253
+ const domainMatches = filterItem(domain);
254
+
255
+ // Check if this domain is a subdomain of another domain
256
+ const isSubdomain = domains.some((parentDomain: any) => {
257
+ const subdomains = parentDomain.domains || [];
258
+ return subdomains.some((subdomain: any) => subdomain.data.id === domain.id);
259
+ });
260
+
261
+ // If this is a parent domain, check if any of its subdomains match
262
+ let hasMatchingSubdomains = false;
263
+ if (!isSubdomain) {
264
+ const subdomains = domain.domains || [];
265
+ hasMatchingSubdomains = domains.some((potentialSubdomain: any) =>
266
+ subdomains.some((subdomain: any) => subdomain.data.id === potentialSubdomain.id && filterItem(potentialSubdomain))
267
+ );
268
+ }
269
+
270
+ // Include domain if:
271
+ // 1. The domain itself matches the search
272
+ // 2. It's a parent domain and has matching subdomains
273
+ // 3. It's a subdomain and matches the search
274
+ if (domainMatches || hasMatchingSubdomains || (isSubdomain && domainMatches)) {
275
+ filteredDomains.push(domain);
276
+ }
277
+
278
+ // If this is a subdomain that matches, also include its parent domain
279
+ if (isSubdomain && domainMatches) {
280
+ const parentDomain = domains.find((parentDomain: any) => {
281
+ const subdomains = parentDomain.domains || [];
282
+ return subdomains.some((subdomain: any) => subdomain.data.id === domain.id);
283
+ });
284
+
285
+ if (parentDomain && !filteredDomains.some((d: any) => d.id === parentDomain.id)) {
286
+ filteredDomains.push(parentDomain);
287
+ }
288
+ }
289
+ });
290
+
291
+ return filteredDomains;
292
+ };
293
+
244
294
  return {
245
295
  'context-map': data['context-map']?.filter(filterItem) || [],
246
- domains: data.domains?.filter(filterItem) || [],
296
+ domains: data.domains ? filterDomains(data.domains) : [],
247
297
  services:
248
298
  data.services
249
299
  ?.map((service: ServiceItem) => ({
@@ -295,7 +345,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
295
345
  const activeElement = document.querySelector('[data-active="true"]');
296
346
  if (activeElement) {
297
347
  // Add y offset to the scroll position
298
- activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
348
+ activeElement.scrollIntoView({ behavior: 'instant', block: 'center' });
299
349
  }
300
350
  }, []);
301
351
 
@@ -310,6 +360,65 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
310
360
  setSearchTerm(e.target.value);
311
361
  }, []);
312
362
 
363
+ const collapseAll = useCallback(() => {
364
+ const newCollapsedState: { [key: string]: boolean } = {};
365
+
366
+ // Collapse all domains
367
+ filteredData.domains?.forEach((domain: any) => {
368
+ newCollapsedState[domain.href] = true;
369
+ newCollapsedState[`${domain.href}-entities`] = true;
370
+ newCollapsedState[`${domain.href}-subdomains`] = true;
371
+ });
372
+
373
+ // Collapse all services
374
+ filteredData.services?.forEach((service: any) => {
375
+ newCollapsedState[service.href] = true;
376
+ newCollapsedState[`${service.href}-specifications`] = true;
377
+ newCollapsedState[`${service.href}-receives`] = true;
378
+ newCollapsedState[`${service.href}-sends`] = true;
379
+ newCollapsedState[`${service.href}-entities`] = true;
380
+ });
381
+
382
+ setCollapsedGroups(newCollapsedState);
383
+ setIsExpanded(false);
384
+ }, [filteredData]);
385
+
386
+ const expandAll = useCallback(() => {
387
+ const newCollapsedState: { [key: string]: boolean } = {};
388
+
389
+ // Expand all domains
390
+ filteredData.domains?.forEach((domain: any) => {
391
+ newCollapsedState[domain.href] = false;
392
+ newCollapsedState[`${domain.href}-entities`] = false;
393
+ newCollapsedState[`${domain.href}-subdomains`] = false;
394
+ });
395
+
396
+ // Expand all services
397
+ filteredData.services?.forEach((service: any) => {
398
+ newCollapsedState[service.href] = false;
399
+ newCollapsedState[`${service.href}-specifications`] = false;
400
+ newCollapsedState[`${service.href}-receives`] = false;
401
+ newCollapsedState[`${service.href}-sends`] = false;
402
+ newCollapsedState[`${service.href}-entities`] = false;
403
+ });
404
+
405
+ setCollapsedGroups(newCollapsedState);
406
+ setIsExpanded(true);
407
+ }, [filteredData]);
408
+
409
+ const toggleExpandCollapse = useCallback(() => {
410
+ if (isExpanded) {
411
+ collapseAll();
412
+ } else {
413
+ expandAll();
414
+ }
415
+ }, [isExpanded, collapseAll, expandAll]);
416
+
417
+ const hideSidebar = useCallback(() => {
418
+ // Dispatch custom event that the Astro layout will listen for
419
+ window.dispatchEvent(new CustomEvent('sidebarToggle', { detail: { action: 'hide' } }));
420
+ }, []);
421
+
313
422
  const isDomainSubDomain = useMemo(() => {
314
423
  return (domain: any) => {
315
424
  const domains = data.domains || [];
@@ -320,6 +429,128 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
320
429
  };
321
430
  }, [data.domains]);
322
431
 
432
+ // Helper function to get parent domains (domains that are not subdomains)
433
+ const getParentDomains = useMemo(() => {
434
+ return (domains: any[]) => {
435
+ return domains.filter((domain: any) => !isDomainSubDomain(domain));
436
+ };
437
+ }, [isDomainSubDomain]);
438
+
439
+ // Helper function to get subdomains for a specific parent domain
440
+ const getSubdomainsForParent = useMemo(() => {
441
+ return (parentDomain: any, allDomains: any[]) => {
442
+ const subdomains = parentDomain.domains || [];
443
+ return allDomains.filter((domain: any) => subdomains.some((subdomain: any) => subdomain.data.id === domain.id));
444
+ };
445
+ }, []);
446
+
447
+ // Component to render a single domain item
448
+ const DomainItem = React.memo(
449
+ ({ item, isSubdomain = false, nestingLevel = 0 }: { item: any; isSubdomain?: boolean; nestingLevel?: number }) => {
450
+ const marginLeft = nestingLevel > 0 ? `ml-${nestingLevel * 4}` : '';
451
+
452
+ return (
453
+ <div className={`flex items-center ${marginLeft}`}>
454
+ <button
455
+ onClick={(e) => {
456
+ e.stopPropagation();
457
+ toggleGroupCollapse(item.href);
458
+ }}
459
+ className="p-1 hover:bg-gray-100 rounded-md"
460
+ >
461
+ <div className={`transition-transform duration-150 ${collapsedGroups[item.href] ? '' : 'rotate-180'}`}>
462
+ <ChevronDownIcon className="h-3 w-3 text-gray-500" />
463
+ </div>
464
+ </button>
465
+ <button
466
+ onClick={(e) => {
467
+ e.stopPropagation();
468
+ toggleGroupCollapse(item.href);
469
+ }}
470
+ className={`flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
471
+ decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
472
+ }`}
473
+ >
474
+ <span className="truncate">
475
+ <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
476
+ </span>
477
+ <span className="text-yellow-600 ml-2 text-[10px] font-medium bg-yellow-50 px-2 py-0.5 rounded">
478
+ {isSubdomain ? 'SUBDOMAIN' : 'DOMAIN'}
479
+ </span>
480
+ </button>
481
+ </div>
482
+ );
483
+ }
484
+ );
485
+
486
+ // Component to render domain content (Overview, Architecture, etc.)
487
+ const DomainContent = React.memo(({ item, nestingLevel = 0 }: { item: any; nestingLevel?: number }) => {
488
+ const marginLeft = nestingLevel > 0 ? `ml-${nestingLevel * 4}` : '';
489
+
490
+ return (
491
+ <div
492
+ className={`overflow-hidden transition-[height] duration-150 ease-out ${collapsedGroups[item.href] ? 'h-0' : 'h-auto'}`}
493
+ >
494
+ <div className={`space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-1 ${marginLeft}`}>
495
+ <a
496
+ href={`${item.href}`}
497
+ data-active={decodedCurrentPath === item.href}
498
+ className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
499
+ decodedCurrentPath === item.href ? 'bg-purple-100 ' : 'hover:bg-purple-100'
500
+ }`}
501
+ >
502
+ <span className="truncate">Overview</span>
503
+ </a>
504
+ {!isVisualizer && (
505
+ <a
506
+ href={buildUrlWithParams('/architecture/docs/services', {
507
+ serviceIds: item.services.map((service: any) => service.data.id).join(','),
508
+ domainId: item.id,
509
+ domainName: item.name,
510
+ })}
511
+ data-active={window.location.href.includes(`domainId=${item.id}`)}
512
+ className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
513
+ window.location.href.includes(`domainId=${item.id}`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
514
+ }`}
515
+ >
516
+ <span className="truncate">Architecture</span>
517
+ </a>
518
+ )}
519
+ {!isVisualizer && (
520
+ <a
521
+ href={buildUrl(`/docs/domains/${item.id}/language`)}
522
+ data-active={decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)}
523
+ className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
524
+ decodedCurrentPath.includes(`/docs/domains/${item.id}/language`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
525
+ }`}
526
+ >
527
+ <span className="truncate">Ubiquitous Language</span>
528
+ </a>
529
+ )}
530
+ {item.entities.length > 0 && !isVisualizer && (
531
+ <CollapsibleGroup
532
+ isCollapsed={collapsedGroups[`${item.href}-entities`]}
533
+ onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
534
+ title={
535
+ <button
536
+ onClick={(e) => {
537
+ e.stopPropagation();
538
+ toggleGroupCollapse(`${item.href}-entities`);
539
+ }}
540
+ className="truncate underline ml-2 text-xs mb-1 py-1"
541
+ >
542
+ Entities ({item.entities.length})
543
+ </button>
544
+ }
545
+ >
546
+ <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} searchTerm={debouncedSearchTerm} />
547
+ </CollapsibleGroup>
548
+ )}
549
+ </div>
550
+ </div>
551
+ );
552
+ });
553
+
323
554
  if (!isInitialized) return null;
324
555
 
325
556
  const hasNoResults =
@@ -332,13 +563,35 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
332
563
 
333
564
  return (
334
565
  <nav ref={navRef} className="space-y-4 text-gray-800 px-3 py-4">
335
- <input
336
- type="text"
337
- value={searchTerm}
338
- onChange={handleSearchChange}
339
- placeholder="Quick search..."
340
- className="w-full p-2 text-sm rounded-md border border-gray-200 h-[30px]"
341
- />
566
+ <div className="flex gap-2">
567
+ <input
568
+ type="text"
569
+ value={searchTerm}
570
+ onChange={handleSearchChange}
571
+ placeholder="Quick search..."
572
+ className="flex-1 p-2 text-sm rounded-md border border-gray-200 h-[30px]"
573
+ />
574
+ <div className="flex gap-1">
575
+ <button
576
+ onClick={toggleExpandCollapse}
577
+ title={isExpanded ? 'Collapse All' : 'Expand All'}
578
+ className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded-md border border-gray-200 h-[30px] flex items-center justify-center"
579
+ >
580
+ {isExpanded ? (
581
+ <ChevronDoubleUpIcon className="h-4 w-4 text-gray-600" />
582
+ ) : (
583
+ <ChevronDoubleDownIcon className="h-4 w-4 text-gray-600" />
584
+ )}
585
+ </button>
586
+ <button
587
+ onClick={hideSidebar}
588
+ title="Hide Sidebar"
589
+ className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded-md border border-gray-200 h-[30px] flex items-center justify-center"
590
+ >
591
+ <PanelLeft className="h-4 w-4 text-gray-600" />
592
+ </button>
593
+ </div>
594
+ </div>
342
595
  <div className="space-y-2 divide-y divide-gray-200/80">
343
596
  {hasNoResults ? (
344
597
  <NoResultsFound searchTerm={debouncedSearchTerm} />
@@ -352,6 +605,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
352
605
  <li key={item.href}>
353
606
  <a
354
607
  href={item.href}
608
+ data-active={decodedCurrentPath === item.href}
355
609
  className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
356
610
  decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
357
611
  }`}
@@ -372,104 +626,50 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
372
626
  {filteredData['domains'] && (
373
627
  <div className={`${isVisualizer ? 'pt-4 pb-2' : 'p-0'}`}>
374
628
  <ul className="space-y-2">
375
- {filteredData['domains'].map((item: any) => (
376
- <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
377
- <div className="flex items-center">
378
- <button
379
- onClick={(e) => {
380
- e.stopPropagation();
381
- toggleGroupCollapse(item.href);
382
- }}
383
- className="p-1 hover:bg-gray-100 rounded-md"
384
- >
385
- <div className={`transition-transform duration-150 ${collapsedGroups[item.href] ? '' : 'rotate-180'}`}>
386
- <ChevronDownIcon className="h-3 w-3 text-gray-500" />
387
- </div>
388
- </button>
389
- <button
390
- onClick={(e) => {
391
- e.stopPropagation();
392
- toggleGroupCollapse(item.href);
393
- }}
394
- className={`flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
395
- decodedCurrentPath === item.href
396
- }`}
397
- >
398
- <span className="truncate">
399
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
400
- </span>
401
- <span className="text-yellow-600 ml-2 text-[10px] font-medium bg-yellow-50 px-2 py-0.5 rounded">
402
- {isDomainSubDomain(item) ? 'SUBDOMAIN' : 'DOMAIN'}
403
- </span>
404
- </button>
405
- </div>
406
- <div
407
- className={`overflow-hidden transition-[height] duration-150 ease-out ${
408
- collapsedGroups[item.href] ? 'h-0' : 'h-auto'
409
- }`}
410
- >
411
- <div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-1">
412
- <a
413
- href={`${item.href}`}
414
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
415
- decodedCurrentPath === item.href ? 'bg-purple-100 ' : 'hover:bg-purple-100'
416
- }`}
417
- >
418
- <span className="truncate">Overview</span>
419
- </a>
420
- {!isVisualizer && (
421
- <a
422
- href={buildUrlWithParams('/architecture/docs/services', {
423
- serviceIds: item.services.map((service: any) => service.data.id).join(','),
424
- domainId: item.id,
425
- domainName: item.name,
426
- })}
427
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
428
- window.location.href.includes(`domainId=${item.id}`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
429
- }`}
430
- >
431
- <span className="truncate">Architecture</span>
432
- </a>
433
- )}
434
- {!isVisualizer && (
435
- <a
436
- href={buildUrl(`/docs/domains/${item.id}/language`)}
437
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
438
- decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)
439
- ? 'bg-purple-100 '
440
- : 'hover:bg-purple-100'
441
- }`}
442
- >
443
- <span className="truncate">Ubiquitous Language</span>
444
- </a>
445
- )}
446
- {item.entities.length > 0 && !isVisualizer && (
629
+ {getParentDomains(filteredData['domains'] || []).map((parentDomain: any) => {
630
+ const subdomains = getSubdomainsForParent(parentDomain, filteredData['domains'] || []);
631
+
632
+ return (
633
+ <li key={parentDomain.href} className="space-y-0" data-active={decodedCurrentPath === parentDomain.href}>
634
+ <DomainItem item={parentDomain} isSubdomain={false} />
635
+ <DomainContent item={parentDomain} />
636
+
637
+ {/* Render nested subdomains */}
638
+ {subdomains.length > 0 && !collapsedGroups[parentDomain.href] && (
639
+ <div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-2">
447
640
  <CollapsibleGroup
448
- isCollapsed={collapsedGroups[`${item.href}-entities`]}
449
- onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
641
+ isCollapsed={collapsedGroups[`${parentDomain.href}-subdomains`]}
642
+ onToggle={() => toggleGroupCollapse(`${parentDomain.href}-subdomains`)}
450
643
  title={
451
644
  <button
452
645
  onClick={(e) => {
453
646
  e.stopPropagation();
454
- toggleGroupCollapse(`${item.href}-entities`);
647
+ toggleGroupCollapse(`${parentDomain.href}-subdomains`);
455
648
  }}
456
649
  className="truncate underline ml-2 text-xs mb-1 py-1"
457
650
  >
458
- Entities ({item.entities.length})
651
+ Subdomains ({subdomains.length})
459
652
  </button>
460
653
  }
461
654
  >
462
- <MessageList
463
- messages={item.entities}
464
- decodedCurrentPath={decodedCurrentPath}
465
- searchTerm={debouncedSearchTerm}
466
- />
655
+ <div className="space-y-2">
656
+ {subdomains.map((subdomain: any) => (
657
+ <div
658
+ key={subdomain.href}
659
+ className="space-y-0"
660
+ data-active={decodedCurrentPath === subdomain.href}
661
+ >
662
+ <DomainItem item={subdomain} isSubdomain={true} nestingLevel={1} />
663
+ <DomainContent item={subdomain} nestingLevel={1} />
664
+ </div>
665
+ ))}
666
+ </div>
467
667
  </CollapsibleGroup>
468
- )}
469
- </div>
470
- </div>
471
- </li>
472
- ))}
668
+ </div>
669
+ )}
670
+ </li>
671
+ );
672
+ })}
473
673
  </ul>
474
674
  </div>
475
675
  )}
@@ -478,27 +678,30 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
478
678
  <div className="pt-4 pb-2">
479
679
  <ul className="space-y-4">
480
680
  {filteredData['services'].map((item: any) => (
481
- <ServiceItem
482
- key={item.href}
483
- item={item}
484
- decodedCurrentPath={decodedCurrentPath}
485
- collapsedGroups={collapsedGroups}
486
- toggleGroupCollapse={toggleGroupCollapse}
487
- isVisualizer={isVisualizer}
488
- searchTerm={debouncedSearchTerm}
489
- />
681
+ <li key={item.href} data-active={decodedCurrentPath === item.href}>
682
+ <ServiceItem
683
+ key={item.href}
684
+ item={item}
685
+ decodedCurrentPath={decodedCurrentPath}
686
+ collapsedGroups={collapsedGroups}
687
+ toggleGroupCollapse={toggleGroupCollapse}
688
+ isVisualizer={isVisualizer}
689
+ searchTerm={debouncedSearchTerm}
690
+ />
691
+ </li>
490
692
  ))}
491
693
  </ul>
492
694
  </div>
493
695
  )}
494
696
 
495
- {filteredData['messagesNotInService'] && (
697
+ {filteredData['messagesNotInService'] && filteredData['messagesNotInService'].length > 0 && (
496
698
  <div className="pt-4 pb-2">
497
699
  <ul className="space-y-4">
498
700
  {filteredData['messagesNotInService'].map((item: any) => (
499
701
  <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
500
702
  <a
501
703
  href={item.href}
704
+ data-active={decodedCurrentPath === item.href}
502
705
  className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
503
706
  decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
504
707
  }`}
@@ -525,6 +728,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
525
728
  <li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
526
729
  <a
527
730
  href={item.href}
731
+ data-active={decodedCurrentPath === item.href}
528
732
  className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
529
733
  decodedCurrentPath === item.href ? 'bg-cyan-100 text-cyan-900' : 'hover:bg-purple-100'
530
734
  }`}