@eventcatalog/core 3.0.0-beta.8 → 3.0.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 (148) hide show
  1. package/README.md +41 -98
  2. package/dist/__mocks__/astro-content.cjs +32 -0
  3. package/dist/__mocks__/astro-content.d.cts +13 -0
  4. package/dist/__mocks__/astro-content.d.ts +13 -0
  5. package/dist/__mocks__/astro-content.js +7 -0
  6. package/dist/analytics/analytics.cjs +1 -1
  7. package/dist/analytics/analytics.js +2 -2
  8. package/dist/analytics/log-build.cjs +1 -1
  9. package/dist/analytics/log-build.js +3 -3
  10. package/dist/catalog-to-astro-content-directory.cjs +2 -19
  11. package/dist/catalog-to-astro-content-directory.d.cts +1 -2
  12. package/dist/catalog-to-astro-content-directory.d.ts +1 -2
  13. package/dist/catalog-to-astro-content-directory.js +3 -5
  14. package/dist/{chunk-R2BJ7MJG.js → chunk-6Z6ARMQS.js} +1 -17
  15. package/dist/{chunk-LQUXA3NB.js → chunk-BYP43AAT.js} +1 -1
  16. package/dist/{chunk-UTHNQFM7.js → chunk-E5Q7TZYT.js} +1 -1
  17. package/dist/{chunk-KEYJ3FB3.js → chunk-EKGR533N.js} +1 -1
  18. package/dist/{chunk-7MCE4J6I.js → chunk-KF5PARQK.js} +1 -1
  19. package/dist/{chunk-I3QUYHIK.js → chunk-VO5WYA44.js} +1 -1
  20. package/dist/constants.cjs +1 -1
  21. package/dist/constants.js +1 -1
  22. package/dist/eventcatalog.cjs +20 -64
  23. package/dist/eventcatalog.config.d.cts +4 -0
  24. package/dist/eventcatalog.config.d.ts +4 -0
  25. package/dist/eventcatalog.js +26 -52
  26. package/dist/generate.cjs +1 -1
  27. package/dist/generate.js +3 -3
  28. package/dist/utils/cli-logger.cjs +1 -1
  29. package/dist/utils/cli-logger.js +2 -2
  30. package/eventcatalog/astro.config.mjs +4 -1
  31. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  32. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  33. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  34. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  35. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +994 -0
  36. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  37. package/eventcatalog/src/components/Grids/DomainGrid.tsx +310 -173
  38. package/eventcatalog/src/components/Grids/MessageGrid.tsx +299 -180
  39. package/eventcatalog/src/components/Grids/specification-utils.ts +106 -0
  40. package/eventcatalog/src/components/Header.astro +25 -5
  41. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  42. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +95 -90
  43. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +144 -0
  44. package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +34 -8
  45. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -2
  46. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +140 -109
  47. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +5 -14
  48. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +247 -59
  49. package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +64 -126
  50. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +41 -43
  51. package/eventcatalog/src/components/Search/Search.astro +2 -2
  52. package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
  53. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +6 -3
  54. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +44 -16
  55. package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
  56. package/eventcatalog/src/components/Tables/Table.tsx +96 -77
  57. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +108 -74
  58. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +74 -55
  59. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +36 -36
  60. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +110 -77
  61. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +105 -94
  62. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +31 -26
  63. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +115 -215
  64. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +145 -243
  65. package/eventcatalog/src/content.config.ts +1 -13
  66. package/eventcatalog/src/enterprise/ai/chat-api.ts +360 -0
  67. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  68. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  69. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  70. package/eventcatalog/src/layouts/Footer.astro +8 -5
  71. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +133 -117
  72. package/eventcatalog/src/pages/_index.astro +243 -559
  73. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +8 -2
  74. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +9 -5
  75. package/eventcatalog/src/pages/directory/[type]/index.astro +6 -0
  76. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  77. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  78. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  79. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +10 -7
  80. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +194 -121
  81. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +94 -70
  82. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  83. package/eventcatalog/src/pages/docs/users/[id]/index.astro +56 -45
  84. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  85. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  86. package/eventcatalog/src/pages/schemas/explorer/index.astro +7 -157
  87. package/eventcatalog/src/pages/studio.astro +124 -72
  88. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  89. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +10 -1
  90. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +17 -7
  91. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +10 -1
  92. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +11 -4
  93. package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +14 -0
  94. package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
  95. package/eventcatalog/src/utils/collections/channels.ts +0 -2
  96. package/eventcatalog/src/utils/collections/commands.ts +0 -2
  97. package/eventcatalog/src/utils/collections/containers.ts +0 -2
  98. package/eventcatalog/src/utils/collections/domains.ts +0 -2
  99. package/eventcatalog/src/utils/collections/entities.ts +0 -2
  100. package/eventcatalog/src/utils/collections/events.ts +0 -2
  101. package/eventcatalog/src/utils/collections/flows.ts +0 -2
  102. package/eventcatalog/src/utils/collections/queries.ts +0 -2
  103. package/eventcatalog/src/utils/collections/schemas.ts +45 -7
  104. package/eventcatalog/src/utils/collections/services.ts +0 -2
  105. package/eventcatalog/src/utils/feature.ts +9 -5
  106. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +1 -1
  107. package/eventcatalog/src/utils/resource-files.ts +86 -0
  108. package/package.json +12 -15
  109. package/default-files-for-collections/changelogs.md +0 -5
  110. package/default-files-for-collections/channels.md +0 -8
  111. package/default-files-for-collections/commands.md +0 -8
  112. package/default-files-for-collections/domains.md +0 -8
  113. package/default-files-for-collections/events.md +0 -8
  114. package/default-files-for-collections/flows.md +0 -11
  115. package/default-files-for-collections/queries.md +0 -8
  116. package/default-files-for-collections/services.md +0 -8
  117. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  118. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  119. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  120. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  121. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  122. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  123. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  124. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  125. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  126. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  127. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  128. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  129. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  130. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  131. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  132. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  133. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  134. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  135. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  136. package/eventcatalog/src/pages/auth/login.astro +0 -280
  137. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  138. package/eventcatalog/src/pages/chat/index.astro +0 -10
  139. package/eventcatalog/src/pages/docs/_default-docs.mdx +0 -25
  140. package/eventcatalog/src/pages/docs/index.astro +0 -33
  141. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  142. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  143. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  144. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  145. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  146. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
  147. /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
  148. /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
@@ -8,64 +8,62 @@ interface SchemaListItemProps {
8
8
  isSelected: boolean;
9
9
  versions: SchemaItem[];
10
10
  onClick: () => void;
11
- itemRef?: React.RefObject<HTMLButtonElement>;
11
+ itemRef?: React.Ref<HTMLButtonElement>;
12
12
  }
13
13
 
14
14
  export default function SchemaListItem({ message, isSelected, versions, onClick, itemRef }: SchemaListItemProps) {
15
15
  const { color, Icon } = getCollectionStyles(message.collection);
16
16
  const hasMultipleVersions = versions.length > 1;
17
17
 
18
+ // Get the schema icon
19
+ const ext = message.schemaExtension?.toLowerCase();
20
+ const hasSchemaIcon = ['openapi', 'asyncapi', 'graphql', 'avro', 'json', 'proto'].includes(ext || '');
21
+ const iconName = ext === 'json' ? 'json-schema' : ext;
22
+
18
23
  return (
19
24
  <button
20
25
  ref={itemRef}
21
26
  onClick={onClick}
22
- className={`w-full text-left p-4 hover:bg-gray-50 transition-colors ${
23
- isSelected ? `bg-${color}-50 border-l-4 border-${color}-500` : 'border-l-4 border-transparent'
27
+ className={`w-full text-left px-3 py-2 transition-all duration-75 group border-l-2 ${
28
+ isSelected ? `bg-${color}-50 border-l-${color}-500` : 'hover:bg-gray-50 border-l-transparent'
24
29
  }`}
25
30
  >
26
- <div className="flex items-start gap-3">
27
- <Icon className={`h-5 w-5 mt-0.5 flex-shrink-0 ${isSelected ? `text-${color}-600` : `text-${color}-500`}`} />
31
+ <div className="flex items-center gap-2.5">
32
+ {/* Collection Icon */}
33
+ <div
34
+ className={`flex-shrink-0 flex items-center justify-center w-7 h-7 rounded-md ${
35
+ isSelected ? `bg-${color}-100` : `bg-${color}-100/60`
36
+ }`}
37
+ >
38
+ <Icon className={`h-3.5 w-3.5 text-${color}-600`} />
39
+ </div>
40
+
28
41
  <div className="flex-1 min-w-0">
29
- <div className="flex items-center justify-between gap-2 mb-1.5">
30
- <div className="flex items-center gap-2 min-w-0">
31
- <h3 className={`text-sm font-semibold truncate ${isSelected ? `text-${color}-900` : 'text-gray-900'}`}>
32
- {message.data.name}
33
- </h3>
34
- <span className="text-xs text-gray-500 flex-shrink-0">v{message.data.version}</span>
35
- </div>
36
- <div className="flex items-center gap-1 flex-shrink-0">
37
- <span className="inline-flex items-center gap-1 rounded-full bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800">
38
- {(() => {
39
- const ext = message.schemaExtension?.toLowerCase();
40
- if (
41
- ext === 'openapi' ||
42
- ext === 'asyncapi' ||
43
- ext === 'graphql' ||
44
- ext === 'avro' ||
45
- ext === 'json' ||
46
- ext === 'proto'
47
- ) {
48
- // Map json extension to json-schema icon
49
- const iconName = ext === 'json' ? 'json-schema' : ext;
50
- const iconPath = buildUrl(`/icons/${iconName}.svg`, true);
51
- return (
52
- <>
53
- <img src={iconPath} alt={`${ext} icon`} className="h-3 w-3" />
54
- {getSchemaTypeLabel(message.schemaExtension)}
55
- </>
56
- );
57
- }
58
- return getSchemaTypeLabel(message.schemaExtension);
59
- })()}
60
- </span>
61
- {hasMultipleVersions && (
62
- <span className="inline-flex items-center rounded-full bg-blue-100 px-1 py-0.5 text-xs font-medium text-blue-700">
63
- {versions.length} versions
64
- </span>
42
+ {/* Name row with version */}
43
+ <div className="flex items-center gap-1.5">
44
+ <span className={`text-[13px] font-semibold truncate ${isSelected ? 'text-gray-900' : 'text-gray-700'}`}>
45
+ {message.data.name}
46
+ </span>
47
+ <span className={`text-[10px] tabular-nums flex-shrink-0 ${isSelected ? 'text-gray-500' : 'text-gray-400'}`}>
48
+ v{message.data.version}
49
+ </span>
50
+ </div>
51
+
52
+ {/* Meta row */}
53
+ <div className="flex items-center gap-1.5 mt-0.5">
54
+ {/* Schema Format */}
55
+ <span className={`inline-flex items-center gap-1 text-[10px] ${isSelected ? 'text-gray-600' : 'text-gray-500'}`}>
56
+ {hasSchemaIcon && (
57
+ <img src={buildUrl(`/icons/${iconName}.svg`, true)} alt={`${ext} icon`} className="h-3 w-3 opacity-70" />
65
58
  )}
66
- </div>
59
+ {getSchemaTypeLabel(message.schemaExtension)}
60
+ </span>
61
+
62
+ {/* Versions count */}
63
+ {hasMultipleVersions && (
64
+ <span className={`text-[10px] ${isSelected ? 'text-gray-500' : 'text-gray-400'}`}>· {versions.length}v</span>
65
+ )}
67
66
  </div>
68
- {message.data.summary && <p className="text-xs text-gray-600 line-clamp-2">{message.data.summary}</p>}
69
67
  </div>
70
68
  </div>
71
69
  </button>
@@ -4,7 +4,7 @@ import SearchModal from './SearchModal.tsx';
4
4
  ---
5
5
 
6
6
  <div>
7
- <div class="relative flex items-center w-10/12">
7
+ <div class="relative flex items-center w-full pr-4">
8
8
  <input
9
9
  id="search-dummy-input"
10
10
  type="text"
@@ -14,7 +14,7 @@ import SearchModal from './SearchModal.tsx';
14
14
  class="block w-full rounded-md caret-transparent border-0 py-1.5 pr-14 pl-10 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 font-light sm:text-sm sm:leading-6 px-4"
15
15
  />
16
16
  <MagnifyingGlassIcon className="absolute inset-y-0 left-0 h-9 w-8 flex items-center pl-4 text-gray-400" />
17
- <div class="absolute inset-y-0 right-0 flex py-1.5 pr-1.5">
17
+ <div class="absolute inset-y-0 right-0 flex py-1.5 pr-6">
18
18
  <kbd class="inline-flex items-center rounded px-1 font-sans text-xs text-gray-400">⌘K</kbd>
19
19
  </div>
20
20
  </div>
@@ -0,0 +1,25 @@
1
+ ---
2
+ /**
3
+ * SearchDataLoader.astro
4
+ *
5
+ * This component loads the sidebar/search data independently of the sidebar UI.
6
+ * It ensures the search functionality works on all pages, even when the nested
7
+ * sidebar is not rendered (e.g., /discover pages).
8
+ */
9
+ import { getNestedSideBarData } from '@stores/sidebar-store/state';
10
+
11
+ const props = await getNestedSideBarData();
12
+ ---
13
+
14
+ <script is:inline define:vars={{ props }}>
15
+ window.sidebarData = props;
16
+ </script>
17
+
18
+ <script>
19
+ import { setSidebarData } from '@stores/sidebar-store';
20
+
21
+ const data = (window as any).sidebarData;
22
+ if (data) {
23
+ setSidebarData(data);
24
+ }
25
+ </script>
@@ -17,7 +17,7 @@ import {
17
17
  ListOrdered,
18
18
  ArrowLeftRight,
19
19
  } from 'lucide-react';
20
- import type { NavNode } from './sidebar-builder';
20
+ import type { NavNode } from '@stores/sidebar-store/state';
21
21
 
22
22
  const cn = (...classes: (string | false | undefined)[]) => classes.filter(Boolean).join(' ');
23
23
 
@@ -178,8 +178,9 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
178
178
  <button
179
179
  onClick={clearSearch}
180
180
  className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
181
+ aria-label="Clear search"
181
182
  >
182
- <X className="w-4 h-4" />
183
+ <X className="w-4 h-4" aria-hidden="true" />
183
184
  </button>
184
185
  )}
185
186
  </div>
@@ -193,8 +194,10 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
193
194
  ? 'bg-purple-50 border-purple-200 text-purple-600'
194
195
  : 'bg-gray-50 border-gray-200 text-gray-400 hover:text-gray-600 hover:bg-gray-100'
195
196
  )}
197
+ aria-label="Filter search results"
198
+ aria-expanded={showFilterDropdown}
196
199
  >
197
- <SlidersHorizontal className="w-4 h-4" />
200
+ <SlidersHorizontal className="w-4 h-4" aria-hidden="true" />
198
201
  {searchFilters.size > 0 && (
199
202
  <span className="absolute -top-1 -right-1 w-4 h-4 bg-purple-600 text-white text-[10px] font-bold rounded-full flex items-center justify-center">
200
203
  {searchFilters.size}
@@ -3,7 +3,7 @@
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import * as LucideIcons from 'lucide-react';
5
5
  import { ChevronRight, ChevronLeft, ChevronDown, Home, Star, FileQuestion } from 'lucide-react';
6
- import type { NavigationData, NavNode, ChildRef } from './sidebar-builder';
6
+ import type { NavNode, ChildRef } from '@stores/sidebar-store/state';
7
7
  import SearchBar from './SearchBar';
8
8
  import { saveState, loadState, saveCollapsedSections, loadCollapsedSections } from './storage';
9
9
  import { useStore } from '@nanostores/react';
@@ -472,9 +472,36 @@ export default function NestedSideBar() {
472
472
  // Show loading state if no data yet
473
473
  if (!data || roots.length === 0) {
474
474
  return (
475
- <aside className="w-[315px] h-screen flex flex-col bg-gray-50 border-r border-gray-200">
476
- <div className="px-3 py-2 bg-white border-b border-gray-200">
477
- <span className="text-sm font-semibold text-gray-900">Loading...</span>
475
+ <aside className="w-[315px] h-full flex flex-col font-sans">
476
+ {/* Search skeleton */}
477
+ <div className="px-3 py-3 border-b border-gray-200">
478
+ <div className="h-9 bg-gray-100 rounded-lg animate-pulse" />
479
+ </div>
480
+ {/* Content skeleton */}
481
+ <div className="p-3 space-y-3">
482
+ {/* Group header skeleton */}
483
+ <div className="flex items-center gap-2 px-2 py-1.5">
484
+ <div className="w-3.5 h-3.5 bg-gray-200 rounded animate-pulse" />
485
+ <div className="h-4 w-24 bg-gray-200 rounded animate-pulse" />
486
+ </div>
487
+ {/* Item skeletons */}
488
+ {[1, 2, 3, 4, 5].map((i) => (
489
+ <div key={i} className="flex items-center gap-2.5 px-3 py-1.5 ml-3.5 border-l border-gray-100">
490
+ <div className="w-4 h-4 bg-gray-100 rounded animate-pulse" />
491
+ <div className="h-4 bg-gray-100 rounded animate-pulse" style={{ width: `${60 + ((i * 15) % 40)}%` }} />
492
+ </div>
493
+ ))}
494
+ {/* Second group skeleton */}
495
+ <div className="flex items-center gap-2 px-2 py-1.5 mt-4">
496
+ <div className="w-3.5 h-3.5 bg-gray-200 rounded animate-pulse" />
497
+ <div className="h-4 w-20 bg-gray-200 rounded animate-pulse" />
498
+ </div>
499
+ {[1, 2, 3].map((i) => (
500
+ <div key={`g2-${i}`} className="flex items-center gap-2.5 px-3 py-1.5 ml-3.5 border-l border-gray-100">
501
+ <div className="w-4 h-4 bg-gray-100 rounded animate-pulse" />
502
+ <div className="h-4 bg-gray-100 rounded animate-pulse" style={{ width: `${50 + ((i * 20) % 35)}%` }} />
503
+ </div>
504
+ ))}
478
505
  </div>
479
506
  </aside>
480
507
  );
@@ -698,32 +725,32 @@ export default function NestedSideBar() {
698
725
 
699
726
  const headerContent = (
700
727
  <>
701
- <div className="flex items-center">
728
+ <div className="flex items-center gap-2">
702
729
  {GroupIcon && (
703
- <span className="mr-2 text-gray-900">
704
- <GroupIcon className="w-3.5 h-3.5" />
730
+ <span className="flex items-center justify-center w-5 h-5 rounded bg-gray-100 text-gray-600">
731
+ <GroupIcon className="w-3 h-3" />
705
732
  </span>
706
733
  )}
707
- <span className="text-sm text-black font-semibold">{group.title}</span>
734
+ <span className="text-[13px] text-gray-900 font-semibold tracking-tight">{group.title}</span>
708
735
  </div>
709
736
  {canCollapse && <ChevronDown className={cn('w-4 h-4 text-gray-400 transition-transform', isCollapsed && '-rotate-90')} />}
710
737
  </>
711
738
  );
712
739
 
713
740
  return (
714
- <div key={`group-${groupKey || index}`} className="mb-4 last:mb-2">
741
+ <div key={`group-${groupKey || index}`} className="mb-5 last:mb-2">
715
742
  {canCollapse ? (
716
743
  <button
717
744
  onClick={() => toggleSectionCollapse(groupId)}
718
- className="flex items-center justify-between w-full px-2 py-1.5 pb-1.5 hover:bg-gray-100 rounded transition-colors cursor-pointer"
745
+ className="flex items-center justify-between w-full px-2 py-1.5 hover:bg-gray-50 rounded-md transition-colors cursor-pointer"
719
746
  >
720
747
  {headerContent}
721
748
  </button>
722
749
  ) : (
723
- <div className="flex items-center justify-between px-2 py-1.5 pb-1.5">{headerContent}</div>
750
+ <div className="flex items-center justify-between px-2 py-1.5">{headerContent}</div>
724
751
  )}
725
752
  {!isCollapsed && (
726
- <div className="flex flex-col gap-0.5 border-l ml-3.5 border-gray-100">
753
+ <div className="flex flex-col gap-0.5 border-l ml-4 mt-1 border-gray-200">
727
754
  {visibleChildren.map((childRef, childIndex) => {
728
755
  const child = resolveRef(childRef);
729
756
  if (!child) return null;
@@ -776,6 +803,7 @@ export default function NestedSideBar() {
776
803
  <IconComponent className="w-4 h-4" />
777
804
  </span>
778
805
  )}
806
+ {item.leftIcon && <img src={item.leftIcon} alt="" className="w-4 h-4 flex-shrink-0" />}
779
807
  <span
780
808
  className={cn(
781
809
  'text-[13px] truncate',
@@ -809,9 +837,9 @@ export default function NestedSideBar() {
809
837
  );
810
838
 
811
839
  const baseClasses =
812
- 'group flex items-center justify-between w-full px-3 py-1 rounded-lg cursor-pointer text-left transition-colors hover:bg-gray-100 active:bg-gray-200';
840
+ 'group flex items-center justify-between w-full px-3 py-1.5 rounded-lg cursor-pointer text-left transition-colors hover:bg-gray-100 active:bg-gray-200';
813
841
  const parentClasses = itemHasChildren ? 'font-medium' : '';
814
- const activeClasses = isActive ? 'bg-gray-200 hover:bg-gray-200 border-l-4 border-black rounded-l-none' : '';
842
+ const activeClasses = isActive ? 'bg-purple-50 hover:bg-purple-50 border-l-2 border-purple-600 rounded-l-none' : '';
815
843
 
816
844
  // Leaf item with href → render as link
817
845
  if (item.href && !itemHasChildren) {
@@ -1000,7 +1028,7 @@ export default function NestedSideBar() {
1000
1028
  {/* Favorites Section */}
1001
1029
  {favorites.length > 0 && isTopLevel && (
1002
1030
  <div className="mb-6">
1003
- <div className="flex items-center px-2 py-2 pb-2">
1031
+ <div className="flex items-center px-2 py-1.5">
1004
1032
  <Star className="w-3.5 h-3.5 mr-2 text-amber-400 fill-current" />
1005
1033
  <span className="text-sm text-black font-semibold">Favorites</span>
1006
1034
  </div>
@@ -1015,7 +1043,7 @@ export default function NestedSideBar() {
1015
1043
  onClick={() => navigateToFavorite(fav)}
1016
1044
  className={cn(
1017
1045
  'group flex items-center justify-between w-full px-3 py-1.5 rounded-lg cursor-pointer text-left transition-colors hover:bg-amber-50 active:bg-amber-100',
1018
- isActive && 'bg-gray-200 hover:bg-gray-200 border-l-4 border-black rounded-l-none'
1046
+ isActive && 'bg-purple-50 hover:bg-purple-50 border-l-2 border-purple-600 rounded-l-none'
1019
1047
  )}
1020
1048
  >
1021
1049
  <div className="flex items-center gap-2.5 min-w-0 flex-1">
@@ -2,26 +2,11 @@
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
 
4
4
  interface Props extends Omit<HTMLAttributes<'div'>, 'children'> {}
5
- import { getNestedSideBarData } from './NestedSideBar/sidebar-builder';
6
5
  import NestedSideBar from './NestedSideBar';
7
6
  import { ClientRouter } from 'astro:transitions';
8
-
9
- const props = await getNestedSideBarData();
10
7
  ---
11
8
 
12
9
  <div {...Astro.props}>
13
10
  <ClientRouter />
14
11
  <NestedSideBar client:only="react" />
15
12
  </div>
16
-
17
- <script is:inline define:vars={{ props }}>
18
- window.sidebarData = props;
19
- </script>
20
-
21
- <script>
22
- import { setSidebarData } from '@stores/sidebar-store';
23
- const data = (window as any).sidebarData;
24
- if (data) {
25
- setSidebarData(data);
26
- }
27
- </script>
@@ -11,6 +11,7 @@ import {
11
11
  type ColumnFiltersState,
12
12
  } from '@tanstack/react-table';
13
13
  import DebouncedInput from './DebouncedInput';
14
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, SearchX } from 'lucide-react';
14
15
 
15
16
  import { getColumnsByCollection } from './columns';
16
17
  import { useEffect, useMemo, useState } from 'react';
@@ -240,23 +241,26 @@ export const Table = <T extends TCollectionTypes>({
240
241
  },
241
242
  });
242
243
 
244
+ const totalResults = table.getPrePaginationRowModel().rows.length;
245
+ const hasResults = table.getRowModel().rows.length > 0;
246
+
243
247
  return (
244
248
  <div>
245
- {/* <div className='text-right text-gray-400'>{table.getPrePaginationRowModel().rows.length} results</div> */}
246
- <div className=" bg-gray-100/20 rounded-md border-2 border-gray-200 shadow-sm ">
247
- <table className="min-w-full divide-y divide-gray-200 rounded-md ">
248
- <thead className="bg-gray-200/50">
249
+ <div className="rounded-lg border border-gray-200 overflow-hidden">
250
+ <table className="min-w-full divide-y divide-gray-200">
251
+ <thead className="bg-gray-50 sticky top-0 z-10">
249
252
  {table.getHeaderGroups().map((headerGroup, index) => (
250
- <tr key={`${headerGroup}-${index}`} className="rounded-tl-lg">
253
+ <tr key={`${headerGroup}-${index}`}>
251
254
  {headerGroup.headers.map((header) => (
252
- <th key={`${header.id}`} className="pl-4 pr-3 text-left text-sm font-semibold text-gray-800 sm:pl-0 ">
253
- <div className="flex flex-col justify-start px-2 py-2 space-y-2">
254
- <div className="text-md">
255
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
256
- </div>
257
- <div className="">
255
+ <th
256
+ key={`${header.id}`}
257
+ className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"
258
+ >
259
+ <div className="flex flex-col gap-2">
260
+ <div>{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}</div>
261
+ <div>
258
262
  {header.column.columnDef.meta?.showFilter !== false && <Filter column={header.column} />}
259
- {header.column.columnDef.meta?.showFilter == false && <div className="h-10" />}
263
+ {header.column.columnDef.meta?.showFilter == false && <div className="h-9" />}
260
264
  </div>
261
265
  </div>
262
266
  </th>
@@ -265,80 +269,94 @@ export const Table = <T extends TCollectionTypes>({
265
269
  ))}
266
270
  </thead>
267
271
 
268
- <tbody className="divide-y divide-gray-300 ">
269
- {table.getRowModel().rows.map((row, index) => (
270
- <tr key={`${row.id}-${index}`}>
271
- {row.getVisibleCells().map((cell) => (
272
- <td
273
- key={cell.id}
274
- className={` py-4 pl-4 pr-3 text-sm font-medium text-gray-900 ${cell.column.columnDef.meta?.className}`}
275
- >
276
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
277
- </td>
278
- ))}
272
+ <tbody className="bg-white divide-y divide-gray-100">
273
+ {hasResults ? (
274
+ table.getRowModel().rows.map((row, index) => (
275
+ <tr key={`${row.id}-${index}`} className="hover:bg-gray-50 transition-colors">
276
+ {row.getVisibleCells().map((cell) => (
277
+ <td
278
+ key={cell.id}
279
+ className={`px-4 py-3 text-sm text-gray-700 ${cell.column.columnDef.meta?.className || ''}`}
280
+ >
281
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
282
+ </td>
283
+ ))}
284
+ </tr>
285
+ ))
286
+ ) : (
287
+ <tr>
288
+ <td colSpan={table.getAllColumns().length} className="px-4 py-12 text-center">
289
+ <div className="flex flex-col items-center justify-center text-gray-500">
290
+ <SearchX className="w-10 h-10 text-gray-300 mb-3" />
291
+ <p className="text-sm font-medium text-gray-600">No results found</p>
292
+ <p className="text-xs text-gray-400 mt-1">Try adjusting your search or filters</p>
293
+ </div>
294
+ </td>
279
295
  </tr>
280
- ))}
296
+ )}
281
297
  </tbody>
282
298
  </table>
283
299
  </div>
284
- <div>
285
- <div className="h-8" />
286
- <div className="flex items-center gap-2 justify-end px-4 ">
287
- <button
288
- className="relative inline-flex items-center rounded-l-md bg-white px-2 py-1 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
289
- onClick={() => table.setPageIndex(0)}
290
- disabled={!table.getCanPreviousPage()}
291
- >
292
- {'<<'}
293
- </button>
294
- <button
295
- className="relative inline-flex items-center bg-white px-2 py-1 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
296
- onClick={() => table.previousPage()}
297
- disabled={!table.getCanPreviousPage()}
298
- >
299
- {'<'}
300
- </button>
301
- <button
302
- className="relative inline-flex items-center bg-white px-2 py-1 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
303
- onClick={() => table.nextPage()}
304
- disabled={!table.getCanNextPage()}
305
- >
306
- {'>'}
307
- </button>
308
- <button
309
- className="relative inline-flex items-center rounded-r-md bg-white px-2 py-1 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
310
- onClick={() => table.setPageIndex(table.getPageCount() - 1)}
311
- disabled={!table.getCanNextPage()}
312
- >
313
- {'>>'}
314
- </button>
315
- <span className="flex items-center gap-1">
316
- <div>Page</div>
317
- <strong>
318
- {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
319
- </strong>
320
- </span>
321
- <span className="flex items-center gap-1">
322
- | Go to page:
323
- <input
324
- type="number"
325
- defaultValue={table.getState().pagination.pageIndex + 1}
326
- onChange={(e) => {
327
- const page = e.target.value ? Number(e.target.value) - 1 : 0;
328
- table.setPageIndex(page);
329
- }}
330
- className="border border-gray-300 p-1 rounded w-16"
331
- />
332
- </span>
300
+
301
+ {/* Pagination */}
302
+ <div className="flex items-center justify-between px-1 py-4">
303
+ <div className="text-sm text-gray-500">
304
+ {totalResults > 0 && (
305
+ <span>
306
+ Showing <span className="font-medium text-gray-700">{table.getRowModel().rows.length}</span> of{' '}
307
+ <span className="font-medium text-gray-700">{totalResults}</span> results
308
+ </span>
309
+ )}
310
+ </div>
311
+ <div className="flex items-center gap-2">
312
+ <div className="flex items-center rounded-lg border border-gray-200 bg-white">
313
+ <button
314
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors rounded-l-lg"
315
+ onClick={() => table.setPageIndex(0)}
316
+ disabled={!table.getCanPreviousPage()}
317
+ title="First page"
318
+ >
319
+ <ChevronsLeft className="w-4 h-4" />
320
+ </button>
321
+ <button
322
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors border-l border-gray-200"
323
+ onClick={() => table.previousPage()}
324
+ disabled={!table.getCanPreviousPage()}
325
+ title="Previous page"
326
+ >
327
+ <ChevronLeft className="w-4 h-4" />
328
+ </button>
329
+ <span className="px-3 py-2 text-sm text-gray-600 border-l border-r border-gray-200 min-w-[100px] text-center">
330
+ Page <span className="font-medium">{table.getState().pagination.pageIndex + 1}</span> of{' '}
331
+ <span className="font-medium">{table.getPageCount() || 1}</span>
332
+ </span>
333
+ <button
334
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors border-r border-gray-200"
335
+ onClick={() => table.nextPage()}
336
+ disabled={!table.getCanNextPage()}
337
+ title="Next page"
338
+ >
339
+ <ChevronRight className="w-4 h-4" />
340
+ </button>
341
+ <button
342
+ className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors rounded-r-lg"
343
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
344
+ disabled={!table.getCanNextPage()}
345
+ title="Last page"
346
+ >
347
+ <ChevronsRight className="w-4 h-4" />
348
+ </button>
349
+ </div>
333
350
  <select
334
351
  value={table.getState().pagination.pageSize}
335
352
  onChange={(e) => {
336
353
  table.setPageSize(Number(e.target.value));
337
354
  }}
355
+ className="px-3 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-colors"
338
356
  >
339
357
  {[10, 20, 30, 40, 50].map((pageSize) => (
340
358
  <option key={pageSize} value={pageSize}>
341
- Show {pageSize}
359
+ {pageSize} per page
342
360
  </option>
343
361
  ))}
344
362
  </select>
@@ -394,6 +412,8 @@ function Filter<T extends TCollectionTypes>({ column }: { column: Column<TData<T
394
412
  return Array.from(column.getFacetedUniqueValues().keys()).sort().slice(0, 2000);
395
413
  }, [column.getFacetedUniqueValues(), filterVariant]);
396
414
 
415
+ const uniqueCount = column.getFacetedUniqueValues().size;
416
+
397
417
  return (
398
418
  <div>
399
419
  {/* Autocomplete suggestions from faceted values feature */}
@@ -406,11 +426,10 @@ function Filter<T extends TCollectionTypes>({ column }: { column: Column<TData<T
406
426
  type="text"
407
427
  value={(columnFilterValue ?? '') as string}
408
428
  onChange={(value) => column.setFilterValue(value)}
409
- placeholder={`Search... ${!column?.columnDef?.meta?.filterVariant ? `(${column.getFacetedUniqueValues().size})` : ''}`}
410
- className="w-full p-2 border shadow rounded"
429
+ placeholder={!column?.columnDef?.meta?.filterVariant ? `Search (${uniqueCount})...` : 'Search...'}
430
+ className="w-full px-3 py-2 text-sm bg-white border border-gray-200 rounded-lg placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-gray-300 transition-colors"
411
431
  list={column.id + 'list'}
412
432
  />
413
- <div className="h-1" />
414
433
  </div>
415
434
  );
416
435
  }