@eventcatalog/core 3.0.0-beta.0 → 3.0.0-beta.10
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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-JB4YT5JY.js → chunk-37KBX24G.js} +1 -1
- package/dist/{chunk-3W6JYTHP.js → chunk-HIZ72XK6.js} +6 -2
- package/dist/{chunk-X4W4YC3U.js → chunk-QYFFI52L.js} +1 -1
- package/dist/{chunk-TQ4HZREX.js → chunk-VNKO4QEO.js} +1 -1
- package/dist/chunk-VY7TVWWV.js +44 -0
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +66 -20
- package/dist/eventcatalog.js +27 -16
- package/dist/generate.cjs +48 -2
- package/dist/generate.js +3 -1
- package/dist/utils/cli-logger.cjs +82 -0
- package/dist/utils/cli-logger.d.cts +10 -0
- package/dist/utils/cli-logger.d.ts +10 -0
- package/dist/utils/cli-logger.js +7 -0
- package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
- package/eventcatalog/src/components/Header.astro +5 -11
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +45 -3
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +0 -10
- package/eventcatalog/src/components/Search/Search.astro +2 -2
- package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +4 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +80 -52
- package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +1 -1
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +1 -1
- package/eventcatalog/src/layouts/DirectoryLayout.astro +1 -1
- package/eventcatalog/src/layouts/DiscoverLayout.astro +1 -1
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +42 -155
- package/eventcatalog/src/pages/auth/login.astro +2 -2
- package/eventcatalog/src/pages/chat/feature.astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -4
- package/eventcatalog/src/pages/docs/custom/feature.astro +1 -1
- package/eventcatalog/src/pages/docs/custom/index.astro +1 -1
- package/eventcatalog/src/pages/plans/index.astro +1 -1
- package/eventcatalog/src/pages/schemas/explorer/index.astro +1 -1
- package/eventcatalog/src/pages/studio.astro +1 -1
- package/eventcatalog/src/pages/unauthorized/index.astro +1 -1
- package/eventcatalog/src/utils/collections/domains.ts +1 -1
- package/eventcatalog/src/utils/collections/types.ts +6 -0
- package/eventcatalog/src/utils/feature.ts +2 -0
- package/eventcatalog/tsconfig.json +2 -1
- package/package.json +3 -2
|
@@ -13,7 +13,7 @@ if (isAuthEnabled()) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const logo = {
|
|
16
|
-
src: ('/' + (catalog?.logo?.src || '
|
|
16
|
+
src: ('/' + (catalog?.logo?.src || '')).replace(/^\/+/, '/'),
|
|
17
17
|
alt: catalog?.logo?.alt || 'Event Catalog',
|
|
18
18
|
text: catalog?.logo?.text || 'EventCatalog',
|
|
19
19
|
};
|
|
@@ -30,7 +30,7 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
|
|
|
30
30
|
<div class="flex-shrink-0 flex items-center w-3/12">
|
|
31
31
|
<a href={buildUrl(catalog.landingPage || '/')} class="flex space-x-2 items-center group">
|
|
32
32
|
{
|
|
33
|
-
logo.src && (
|
|
33
|
+
logo.src && logo.src !== '/' && (
|
|
34
34
|
<img alt={logo.alt} src={buildUrl(logo.src, true)} class="w-8 h-8 transition-transform group-hover:scale-105" />
|
|
35
35
|
)
|
|
36
36
|
}
|
|
@@ -42,7 +42,7 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
|
|
|
42
42
|
</a>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
-
<div class="hidden lg:block flex-grow
|
|
45
|
+
<div class="hidden lg:block flex-grow -ml-1">
|
|
46
46
|
<Search />
|
|
47
47
|
</div>
|
|
48
48
|
|
|
@@ -118,10 +118,7 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
|
|
|
118
118
|
href="https://discord.com/invite/3rjaZMmrAm"
|
|
119
119
|
class="block p-1.5 rounded-lg hover:bg-gray-100 transition-colors"
|
|
120
120
|
>
|
|
121
|
-
<img
|
|
122
|
-
src={buildUrl('/icons/discord.svg', true)}
|
|
123
|
-
class="h-6 w-6 opacity-70 hover:opacity-100 transition-opacity"
|
|
124
|
-
/>
|
|
121
|
+
<img src={buildUrl('/icons/discord.svg', true)} class="h-6 w-6 hover:opacity-100 transition-opacity" />
|
|
125
122
|
</a>
|
|
126
123
|
</li>
|
|
127
124
|
<li>
|
|
@@ -129,10 +126,7 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
|
|
|
129
126
|
href="https://github.com/event-catalog/eventcatalog"
|
|
130
127
|
class="block p-1.5 rounded-lg hover:bg-gray-100 transition-colors"
|
|
131
128
|
>
|
|
132
|
-
<img
|
|
133
|
-
src={buildUrl('/icons/github.svg', true)}
|
|
134
|
-
class="h-6 w-6 opacity-70 hover:opacity-100 transition-opacity"
|
|
135
|
-
/>
|
|
129
|
+
<img src={buildUrl('/icons/github.svg', true)} class="h-6 w-6 hover:opacity-100 transition-opacity" />
|
|
136
130
|
</a>
|
|
137
131
|
</li>
|
|
138
132
|
</ul>
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import '@xyflow/react/dist/style.css';
|
|
20
20
|
import { ExternalLink, HistoryIcon } from 'lucide-react';
|
|
21
21
|
import { toPng } from 'html-to-image';
|
|
22
|
-
import { DocumentArrowDownIcon } from '@heroicons/react/24/outline';
|
|
22
|
+
import { DocumentArrowDownIcon, PresentationChartLineIcon } from '@heroicons/react/24/outline';
|
|
23
23
|
// Nodes and edges
|
|
24
24
|
import ServiceNode from './Nodes/Service';
|
|
25
25
|
import FlowNode from './Nodes/Flow';
|
|
@@ -135,6 +135,7 @@ const NodeGraphBuilder = ({
|
|
|
135
135
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
|
136
136
|
const [animateMessages, setAnimateMessages] = useState(false);
|
|
137
137
|
const [activeStepIndex, setActiveStepIndex] = useState<number | null>(null);
|
|
138
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
138
139
|
// const [isStudioModalOpen, setIsStudioModalOpen] = useState(false);
|
|
139
140
|
|
|
140
141
|
// Check if there are channels to determine if we need the visualizer functionality
|
|
@@ -349,6 +350,30 @@ const NodeGraphBuilder = ({
|
|
|
349
350
|
setIsStudioModalOpen(true);
|
|
350
351
|
};
|
|
351
352
|
|
|
353
|
+
const toggleFullScreen = useCallback(() => {
|
|
354
|
+
if (!document.fullscreenElement) {
|
|
355
|
+
reactFlowWrapperRef.current?.requestFullscreen().catch((err) => {
|
|
356
|
+
console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
document.exitFullscreen();
|
|
360
|
+
}
|
|
361
|
+
}, []);
|
|
362
|
+
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
const handleFullscreenChange = () => {
|
|
365
|
+
setIsFullscreen(!!document.fullscreenElement);
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
fitView({ duration: 800 });
|
|
368
|
+
}, 100);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
372
|
+
return () => {
|
|
373
|
+
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
374
|
+
};
|
|
375
|
+
}, [fitView]);
|
|
376
|
+
|
|
352
377
|
const handleExportVisual = useCallback(() => {
|
|
353
378
|
const imageWidth = 1024;
|
|
354
379
|
const imageHeight = 768;
|
|
@@ -563,7 +588,7 @@ const NodeGraphBuilder = ({
|
|
|
563
588
|
const isFlowVisualization = edges.some((edge: Edge) => edge.type === 'flow-edge');
|
|
564
589
|
|
|
565
590
|
return (
|
|
566
|
-
<div ref={reactFlowWrapperRef} className="w-full h-full">
|
|
591
|
+
<div ref={reactFlowWrapperRef} className="w-full h-full bg-gray-50">
|
|
567
592
|
<ReactFlow
|
|
568
593
|
nodeTypes={nodeTypes}
|
|
569
594
|
edgeTypes={edgeTypes}
|
|
@@ -583,7 +608,7 @@ const NodeGraphBuilder = ({
|
|
|
583
608
|
<Panel position="top-center" className="w-full pr-6 ">
|
|
584
609
|
<div className="flex space-x-2 justify-between items-center">
|
|
585
610
|
<div className="flex space-x-2 ml-4">
|
|
586
|
-
<div>
|
|
611
|
+
<div className="relative group">
|
|
587
612
|
<button
|
|
588
613
|
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
|
|
589
614
|
className="py-2.5 px-3 bg-white rounded-md shadow-md hover:bg-purple-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
|
|
@@ -591,6 +616,23 @@ const NodeGraphBuilder = ({
|
|
|
591
616
|
>
|
|
592
617
|
<CogIcon className="h-5 w-5 text-gray-600" />
|
|
593
618
|
</button>
|
|
619
|
+
<div className="absolute top-full left-0 mt-2 px-2 py-1 bg-gray-900 text-white text-xs rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none z-50">
|
|
620
|
+
Settings
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
<div className="relative group">
|
|
624
|
+
<button
|
|
625
|
+
onClick={toggleFullScreen}
|
|
626
|
+
className={`py-2.5 px-3 bg-white rounded-md shadow-md hover:bg-purple-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 ${
|
|
627
|
+
isFullscreen ? 'bg-purple-50 text-purple-600' : ''
|
|
628
|
+
}`}
|
|
629
|
+
aria-label={isFullscreen ? 'Exit presentation mode' : 'Enter presentation mode'}
|
|
630
|
+
>
|
|
631
|
+
<PresentationChartLineIcon className={`h-5 w-5 ${isFullscreen ? 'text-purple-600' : 'text-gray-600'}`} />
|
|
632
|
+
</button>
|
|
633
|
+
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-2 py-1 bg-gray-900 text-white text-xs rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none z-50">
|
|
634
|
+
{isFullscreen ? 'Exit Presentation Mode' : 'Presentation Mode'}
|
|
635
|
+
</div>
|
|
594
636
|
</div>
|
|
595
637
|
|
|
596
638
|
{title && (
|
|
@@ -314,16 +314,6 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
314
314
|
|
|
315
315
|
return (
|
|
316
316
|
<div className="h-full flex flex-col overflow-hidden">
|
|
317
|
-
{/* Compact Header */}
|
|
318
|
-
<div className="flex-shrink-0 border-b border-gray-200 pb-2 mb-3">
|
|
319
|
-
<div>
|
|
320
|
-
<h1 className="text-2xl font-bold text-gray-900">Schema Explorer</h1>
|
|
321
|
-
<p className="mt-0.5 text-xs text-gray-600">
|
|
322
|
-
{filteredMessages.length} schema{filteredMessages.length !== 1 ? 's' : ''} available
|
|
323
|
-
</p>
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
326
|
-
|
|
327
317
|
{/* Split View */}
|
|
328
318
|
<div className="flex-1 flex gap-4 overflow-hidden">
|
|
329
319
|
{/* Left: Filters + Schema List */}
|
|
@@ -4,7 +4,7 @@ import SearchModal from './SearchModal.tsx';
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<div>
|
|
7
|
-
<div class="relative flex items-center">
|
|
7
|
+
<div class="relative flex items-center w-10/12">
|
|
8
8
|
<input
|
|
9
9
|
id="search-dummy-input"
|
|
10
10
|
type="text"
|
|
@@ -13,7 +13,7 @@ import SearchModal from './SearchModal.tsx';
|
|
|
13
13
|
autocomplete="off"
|
|
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
|
-
<MagnifyingGlassIcon className="absolute inset-y-0
|
|
16
|
+
<MagnifyingGlassIcon className="absolute inset-y-0 left-0 h-9 w-8 flex items-center pl-4 text-gray-400" />
|
|
17
17
|
<div class="absolute inset-y-0 right-0 flex py-1.5 pr-1.5">
|
|
18
18
|
<kbd class="inline-flex items-center rounded px-1 font-sans text-xs text-gray-400">⌘K</kbd>
|
|
19
19
|
</div>
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
ArrowUturnLeftIcon,
|
|
21
21
|
StarIcon,
|
|
22
22
|
Square2StackIcon,
|
|
23
|
+
ArrowsRightLeftIcon,
|
|
23
24
|
} from '@heroicons/react/24/outline';
|
|
24
25
|
import { StarIcon as StarIconSolid, CircleStackIcon } from '@heroicons/react/24/solid';
|
|
25
26
|
import { useStore } from '@nanostores/react';
|
|
@@ -33,7 +34,7 @@ const typeIcons: any = {
|
|
|
33
34
|
Command: ChatBubbleLeftIcon,
|
|
34
35
|
Query: QueryIcon,
|
|
35
36
|
Entity: CubeIcon,
|
|
36
|
-
Channel:
|
|
37
|
+
Channel: ArrowsRightLeftIcon,
|
|
37
38
|
Team: UserGroupIcon,
|
|
38
39
|
User: UserIcon,
|
|
39
40
|
Language: BookOpenIcon,
|
|
@@ -68,8 +69,6 @@ function classNames(...classes: (string | boolean | undefined)[]) {
|
|
|
68
69
|
|
|
69
70
|
// Helper to construct URL from key if href is missing
|
|
70
71
|
const getUrlForItem = (node: any, key: string) => {
|
|
71
|
-
if (node.href) return node.href;
|
|
72
|
-
|
|
73
72
|
const parts = key.split(':');
|
|
74
73
|
if (parts.length < 2) return null; // Need at least type:id
|
|
75
74
|
|
|
@@ -83,8 +82,16 @@ const getUrlForItem = (node: any, key: string) => {
|
|
|
83
82
|
// Only show items that have a version to avoid duplicates
|
|
84
83
|
if (!version) return null;
|
|
85
84
|
|
|
85
|
+
// If node has href, use it, otherwise construct from key
|
|
86
|
+
if (node.href) return node.href;
|
|
87
|
+
|
|
86
88
|
// Pluralize type for URL if needed
|
|
87
|
-
|
|
89
|
+
let pluralType = type;
|
|
90
|
+
if (['event', 'command', 'domain', 'service', 'flow', 'container', 'channel'].includes(type)) {
|
|
91
|
+
pluralType = type + 's';
|
|
92
|
+
} else if (type === 'query') {
|
|
93
|
+
pluralType = 'queries';
|
|
94
|
+
}
|
|
88
95
|
|
|
89
96
|
return `/docs/${pluralType}/${id}/${version}`;
|
|
90
97
|
};
|
|
@@ -171,6 +178,7 @@ export default function SearchModal() {
|
|
|
171
178
|
Team: 0,
|
|
172
179
|
Container: 0,
|
|
173
180
|
Design: 0,
|
|
181
|
+
Channel: 0,
|
|
174
182
|
};
|
|
175
183
|
|
|
176
184
|
itemsToCount.forEach((item) => {
|
|
@@ -195,6 +203,7 @@ export default function SearchModal() {
|
|
|
195
203
|
if (counts.Service > 0) dynamicFilters.push({ id: 'Service', name: `Services (${counts.Service})` });
|
|
196
204
|
if (counts.Message > 0) dynamicFilters.push({ id: 'Message', name: `Messages (${counts.Message})` });
|
|
197
205
|
if (counts.Container > 0) dynamicFilters.push({ id: 'Container', name: `Containers (${counts.Container})` });
|
|
206
|
+
if (counts.Channel > 0) dynamicFilters.push({ id: 'Channel', name: `Channels (${counts.Channel})` });
|
|
198
207
|
if (counts.Design > 0) dynamicFilters.push({ id: 'Design', name: `Designs (${counts.Design})` });
|
|
199
208
|
if (counts.Team > 0) dynamicFilters.push({ id: 'Team', name: `Teams & Users (${counts.Team})` });
|
|
200
209
|
|
|
@@ -224,7 +233,7 @@ export default function SearchModal() {
|
|
|
224
233
|
const filteredItems = useMemo(() => {
|
|
225
234
|
if (query === '') {
|
|
226
235
|
// Show favorites when search is empty
|
|
227
|
-
if (favorites.length > 0) {
|
|
236
|
+
if (favorites.length > 0 && activeFilter === 'all') {
|
|
228
237
|
return favorites
|
|
229
238
|
.slice(0, 5)
|
|
230
239
|
.map((fav) => {
|
|
@@ -375,14 +384,14 @@ export default function SearchModal() {
|
|
|
375
384
|
<p className={classNames('text-sm font-medium', active ? 'text-gray-900' : 'text-gray-700')}>
|
|
376
385
|
{item.name}
|
|
377
386
|
</p>
|
|
378
|
-
<div className="flex items-
|
|
387
|
+
<div className="flex items-center gap-2">
|
|
379
388
|
<p
|
|
380
389
|
className={classNames('text-sm flex-shrink-0', active ? 'text-gray-700' : 'text-gray-500')}
|
|
381
390
|
>
|
|
382
391
|
{item.type}
|
|
383
392
|
</p>
|
|
384
393
|
{item.rawNode.summary && (
|
|
385
|
-
<p className={classNames('text-
|
|
394
|
+
<p className={classNames('text-sm truncate', active ? 'text-gray-600' : 'text-gray-400')}>
|
|
386
395
|
• {item.rawNode.summary}
|
|
387
396
|
</p>
|
|
388
397
|
)}
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
Database,
|
|
15
15
|
Waypoints,
|
|
16
16
|
SquareMousePointer,
|
|
17
|
+
ListOrdered,
|
|
18
|
+
ArrowLeftRight,
|
|
17
19
|
} from 'lucide-react';
|
|
18
20
|
import type { NavNode } from './sidebar-builder';
|
|
19
21
|
|
|
@@ -28,6 +30,7 @@ const getBadgeClasses = (badge: string): string => {
|
|
|
28
30
|
query: 'bg-purple-100 text-purple-700',
|
|
29
31
|
message: 'bg-indigo-100 text-indigo-700',
|
|
30
32
|
design: 'bg-teal-100 text-teal-700',
|
|
33
|
+
channel: 'bg-indigo-100 text-indigo-700',
|
|
31
34
|
};
|
|
32
35
|
return badgeColors[badge.toLowerCase()] || 'bg-gray-100 text-gray-600';
|
|
33
36
|
};
|
|
@@ -77,6 +80,7 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
|
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
const filterTypes = [
|
|
83
|
+
{ key: 'channel', label: 'Channels', badge: 'Channel', icon: ArrowLeftRight },
|
|
80
84
|
{ key: 'command', label: 'Commands', badge: 'Command', icon: MessageSquare },
|
|
81
85
|
{ key: 'container', label: 'Data Stores', badge: 'Container', icon: Database },
|
|
82
86
|
{ key: 'design', label: 'Designs', badge: 'Design', icon: SquareMousePointer },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import * as LucideIcons from 'lucide-react';
|
|
5
|
-
import { ChevronRight, ChevronLeft, ChevronDown, Home, Star } from 'lucide-react';
|
|
5
|
+
import { ChevronRight, ChevronLeft, ChevronDown, Home, Star, FileQuestion } from 'lucide-react';
|
|
6
6
|
import type { NavigationData, NavNode, ChildRef } from './sidebar-builder';
|
|
7
7
|
import SearchBar from './SearchBar';
|
|
8
8
|
import { saveState, loadState, saveCollapsedSections, loadCollapsedSections } from './storage';
|
|
@@ -25,6 +25,7 @@ const getBadgeClasses = (badge: string): string => {
|
|
|
25
25
|
query: 'bg-purple-100 text-purple-700',
|
|
26
26
|
message: 'bg-indigo-100 text-indigo-700',
|
|
27
27
|
design: 'bg-teal-100 text-teal-700',
|
|
28
|
+
channel: 'bg-indigo-100 text-indigo-700',
|
|
28
29
|
};
|
|
29
30
|
return badgeColors[badge.toLowerCase()] || 'bg-gray-100 text-gray-600';
|
|
30
31
|
};
|
|
@@ -42,13 +43,15 @@ type NavigationLevel = {
|
|
|
42
43
|
|
|
43
44
|
export default function NestedSideBar() {
|
|
44
45
|
const data = useStore(sidebarStore);
|
|
46
|
+
const favorites = useStore(favoritesStore);
|
|
45
47
|
|
|
46
48
|
// Guard against undefined data (e.g., during hydration)
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
+
// Use useMemo to ensure stable references for roots and nodes
|
|
50
|
+
const roots = useMemo(() => data?.roots ?? [], [data?.roots]);
|
|
51
|
+
const nodes = useMemo(() => data?.nodes ?? {}, [data?.nodes]);
|
|
49
52
|
|
|
50
|
-
const [navigationStack, setNavigationStack] = useState<NavigationLevel[]>([
|
|
51
|
-
{ key: null, entries:
|
|
53
|
+
const [navigationStack, setNavigationStack] = useState<NavigationLevel[]>(() => [
|
|
54
|
+
{ key: null, entries: [], title: 'Documentation' },
|
|
52
55
|
]);
|
|
53
56
|
const [animationKey, setAnimationKey] = useState(0);
|
|
54
57
|
const [slideDirection, setSlideDirection] = useState<'forward' | 'backward' | null>(null);
|
|
@@ -57,7 +60,6 @@ export default function NestedSideBar() {
|
|
|
57
60
|
const [collapsedSections, setCollapsedSections] = useState<Set<string>>(new Set());
|
|
58
61
|
const [showPathPreview, setShowPathPreview] = useState(false);
|
|
59
62
|
const [showFullPath, setShowFullPath] = useState(false);
|
|
60
|
-
const favorites = useStore(favoritesStore);
|
|
61
63
|
const [isSearching, setIsSearching] = useState(false);
|
|
62
64
|
|
|
63
65
|
// Build a lookup map for faster URL navigation
|
|
@@ -113,6 +115,21 @@ export default function NestedSideBar() {
|
|
|
113
115
|
}
|
|
114
116
|
}, []);
|
|
115
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Update navigation stack when roots become available
|
|
120
|
+
*/
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (roots.length > 0) {
|
|
123
|
+
setNavigationStack((prevStack) => {
|
|
124
|
+
// Only update if the current stack has no entries (initial state)
|
|
125
|
+
if (prevStack.length === 1 && prevStack[0].entries.length === 0) {
|
|
126
|
+
return [{ key: null, entries: roots, title: 'Documentation' }];
|
|
127
|
+
}
|
|
128
|
+
return prevStack;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}, [roots]);
|
|
132
|
+
|
|
116
133
|
/**
|
|
117
134
|
* Populate the store with the data when the component mounts or data changes
|
|
118
135
|
*/
|
|
@@ -300,43 +317,47 @@ export default function NestedSideBar() {
|
|
|
300
317
|
const foundNodeKey = findNodeKeyByUrl(url);
|
|
301
318
|
|
|
302
319
|
if (foundNodeKey) {
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
setNavigationStack((currentStack) => {
|
|
321
|
+
// Try to connect to current stack first
|
|
322
|
+
const connectedStack = tryConnectStack(foundNodeKey, currentStack);
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
324
|
+
if (connectedStack) {
|
|
325
|
+
return connectedStack;
|
|
326
|
+
}
|
|
310
327
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
328
|
+
const foundNode = nodes[foundNodeKey];
|
|
329
|
+
if (foundNode && foundNode.pages && foundNode.pages.length > 0) {
|
|
330
|
+
// Fallback: Flattened navigation
|
|
331
|
+
return [
|
|
332
|
+
{ key: null, entries: roots, title: 'Documentation' },
|
|
333
|
+
{ key: foundNodeKey, entries: foundNode.pages, title: foundNode.title, badge: foundNode.badge },
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return currentStack;
|
|
338
|
+
});
|
|
339
|
+
return true;
|
|
320
340
|
} else if (url === '/' || url === '') {
|
|
321
341
|
// Reset to root if we are on homepage
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
setNavigationStack((currentStack) => {
|
|
343
|
+
if (currentStack.length > 1) {
|
|
344
|
+
setSlideDirection('backward');
|
|
345
|
+
setAnimationKey((prev) => prev + 1);
|
|
346
|
+
}
|
|
347
|
+
return [{ key: null, entries: roots, title: 'Documentation' }];
|
|
348
|
+
});
|
|
327
349
|
return true;
|
|
328
350
|
}
|
|
329
351
|
return false;
|
|
330
352
|
},
|
|
331
|
-
[findNodeKeyByUrl, tryConnectStack,
|
|
353
|
+
[findNodeKeyByUrl, tryConnectStack, nodes, roots]
|
|
332
354
|
);
|
|
333
355
|
|
|
334
356
|
/**
|
|
335
357
|
* Restore state from localStorage on mount, or navigate to URL
|
|
336
358
|
*/
|
|
337
359
|
useEffect(() => {
|
|
338
|
-
if (!data || roots.length === 0) return;
|
|
339
|
-
if (isInitialized) return;
|
|
360
|
+
if (!data || roots.length === 0 || isInitialized) return;
|
|
340
361
|
|
|
341
362
|
const currentUrl = window.location.pathname;
|
|
342
363
|
|
|
@@ -387,7 +408,7 @@ export default function NestedSideBar() {
|
|
|
387
408
|
}
|
|
388
409
|
|
|
389
410
|
setIsInitialized(true);
|
|
390
|
-
}, [data, roots,
|
|
411
|
+
}, [data, roots, nodes, isInitialized, buildStackFromPath, findNodeKeyByUrl, tryConnectStack]);
|
|
391
412
|
|
|
392
413
|
/**
|
|
393
414
|
* Save state whenever navigation changes
|
|
@@ -750,10 +771,7 @@ export default function NestedSideBar() {
|
|
|
750
771
|
<div className="flex items-center gap-2.5 min-w-0 flex-1 ">
|
|
751
772
|
{IconComponent && (
|
|
752
773
|
<span
|
|
753
|
-
className={cn(
|
|
754
|
-
'flex items-center justify-center w-5 h-5 flex-shrink-0',
|
|
755
|
-
isActive ? 'text-purple-600' : 'text-gray-500'
|
|
756
|
-
)}
|
|
774
|
+
className={cn('flex items-center justify-center w-5 h-5 flex-shrink-0', isActive ? 'text-black' : 'text-gray-500')}
|
|
757
775
|
>
|
|
758
776
|
<IconComponent className="w-4 h-4" />
|
|
759
777
|
</span>
|
|
@@ -761,7 +779,7 @@ export default function NestedSideBar() {
|
|
|
761
779
|
<span
|
|
762
780
|
className={cn(
|
|
763
781
|
'text-[13px] truncate',
|
|
764
|
-
isActive ? 'text-
|
|
782
|
+
isActive ? 'text-black font-medium' : 'text-gray-600 group-hover:text-gray-900'
|
|
765
783
|
)}
|
|
766
784
|
>
|
|
767
785
|
{item.title}
|
|
@@ -769,20 +787,20 @@ export default function NestedSideBar() {
|
|
|
769
787
|
</div>
|
|
770
788
|
<div className="flex items-center gap-1 flex-shrink-0">
|
|
771
789
|
{canFavorite && (
|
|
772
|
-
<
|
|
790
|
+
<div
|
|
773
791
|
onClick={handleStarClick}
|
|
774
792
|
className={cn(
|
|
775
|
-
'flex items-center justify-center w-5 h-5 rounded transition-colors',
|
|
793
|
+
'flex items-center justify-center w-5 h-5 rounded transition-colors cursor-pointer',
|
|
776
794
|
isFav
|
|
777
795
|
? 'text-amber-400 hover:text-amber-500'
|
|
778
796
|
: 'text-gray-300 opacity-0 group-hover:opacity-100 hover:text-amber-400'
|
|
779
797
|
)}
|
|
780
798
|
>
|
|
781
799
|
<Star className={cn('w-3.5 h-3.5', isFav && 'fill-current')} />
|
|
782
|
-
</
|
|
800
|
+
</div>
|
|
783
801
|
)}
|
|
784
802
|
{itemHasChildren && (
|
|
785
|
-
<span className="flex items-center justify-center w-5 h-5 text-gray-400 group-hover:text-
|
|
803
|
+
<span className="flex items-center justify-center w-5 h-5 text-gray-400 group-hover:text-black group-hover:translate-x-0.5 transition-transform">
|
|
786
804
|
<ChevronRight className="w-4 h-4" />
|
|
787
805
|
</span>
|
|
788
806
|
)}
|
|
@@ -793,7 +811,7 @@ export default function NestedSideBar() {
|
|
|
793
811
|
const baseClasses =
|
|
794
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';
|
|
795
813
|
const parentClasses = itemHasChildren ? 'font-medium' : '';
|
|
796
|
-
const activeClasses = isActive ? 'bg-
|
|
814
|
+
const activeClasses = isActive ? 'bg-gray-200 hover:bg-gray-200 border-l-4 border-black rounded-l-none' : '';
|
|
797
815
|
|
|
798
816
|
// Leaf item with href → render as link
|
|
799
817
|
if (item.href && !itemHasChildren) {
|
|
@@ -899,7 +917,7 @@ export default function NestedSideBar() {
|
|
|
899
917
|
className={cn(
|
|
900
918
|
'flex items-center gap-2 px-2 py-1.5 rounded text-left transition-colors',
|
|
901
919
|
!isCurrentLevel && 'hover:bg-gray-100 cursor-pointer',
|
|
902
|
-
isCurrentLevel && 'bg-
|
|
920
|
+
isCurrentLevel && 'bg-gray-200 cursor-default'
|
|
903
921
|
)}
|
|
904
922
|
style={{ paddingLeft: `${displayIndex * 12 + 8}px` }}
|
|
905
923
|
>
|
|
@@ -909,10 +927,7 @@ export default function NestedSideBar() {
|
|
|
909
927
|
<ChevronRight className="w-3.5 h-3.5 text-gray-300 flex-shrink-0" />
|
|
910
928
|
)}
|
|
911
929
|
<span
|
|
912
|
-
className={cn(
|
|
913
|
-
'text-sm truncate',
|
|
914
|
-
isCurrentLevel ? 'font-medium text-purple-700' : 'text-gray-600'
|
|
915
|
-
)}
|
|
930
|
+
className={cn('text-sm truncate', isCurrentLevel ? 'font-medium text-black' : 'text-gray-600')}
|
|
916
931
|
>
|
|
917
932
|
{level.title}
|
|
918
933
|
</span>
|
|
@@ -1000,14 +1015,14 @@ export default function NestedSideBar() {
|
|
|
1000
1015
|
onClick={() => navigateToFavorite(fav)}
|
|
1001
1016
|
className={cn(
|
|
1002
1017
|
'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',
|
|
1003
|
-
isActive && 'bg-
|
|
1018
|
+
isActive && 'bg-gray-200 hover:bg-gray-200 border-l-4 border-black rounded-l-none'
|
|
1004
1019
|
)}
|
|
1005
1020
|
>
|
|
1006
1021
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
|
1007
1022
|
<span
|
|
1008
1023
|
className={cn(
|
|
1009
1024
|
'text-[14px] truncate',
|
|
1010
|
-
isActive ? 'text-
|
|
1025
|
+
isActive ? 'text-black font-medium' : 'text-gray-600 group-hover:text-gray-900'
|
|
1011
1026
|
)}
|
|
1012
1027
|
>
|
|
1013
1028
|
{fav.title}
|
|
@@ -1024,17 +1039,17 @@ export default function NestedSideBar() {
|
|
|
1024
1039
|
{fav.badge}
|
|
1025
1040
|
</span>
|
|
1026
1041
|
)}
|
|
1027
|
-
<
|
|
1042
|
+
<div
|
|
1028
1043
|
onClick={(e) => {
|
|
1029
1044
|
e.stopPropagation();
|
|
1030
1045
|
if (node) toggleFavorite(fav.nodeKey, node);
|
|
1031
1046
|
}}
|
|
1032
|
-
className="flex items-center justify-center w-5 h-5 text-amber-400 hover:text-amber-500 rounded transition-colors"
|
|
1047
|
+
className="flex items-center justify-center w-5 h-5 text-amber-400 hover:text-amber-500 rounded transition-colors cursor-pointer"
|
|
1033
1048
|
>
|
|
1034
1049
|
<Star className="w-3.5 h-3.5 fill-current" />
|
|
1035
|
-
</
|
|
1050
|
+
</div>
|
|
1036
1051
|
{node?.pages && node.pages.length > 0 && (
|
|
1037
|
-
<span className="flex items-center justify-center w-5 h-5 text-gray-400 group-hover:text-
|
|
1052
|
+
<span className="flex items-center justify-center w-5 h-5 text-gray-400 group-hover:text-black">
|
|
1038
1053
|
<ChevronRight className="w-4 h-4" />
|
|
1039
1054
|
</span>
|
|
1040
1055
|
)}
|
|
@@ -1046,7 +1061,20 @@ export default function NestedSideBar() {
|
|
|
1046
1061
|
</div>
|
|
1047
1062
|
)}
|
|
1048
1063
|
|
|
1049
|
-
{
|
|
1064
|
+
{/* Empty State */}
|
|
1065
|
+
{currentLevel.entries.length === 0 && favorites.length === 0 && (
|
|
1066
|
+
<div className="flex flex-col items-center justify-center px-6 py-12 text-center">
|
|
1067
|
+
<div className="mb-4 p-3 rounded-full bg-gray-100">
|
|
1068
|
+
<FileQuestion className="w-8 h-8 text-gray-400" />
|
|
1069
|
+
</div>
|
|
1070
|
+
<h3 className="text-sm font-semibold text-gray-900 mb-2">Your catalog is empty</h3>
|
|
1071
|
+
<p className="text-xs text-gray-500 leading-relaxed max-w-[240px]">
|
|
1072
|
+
Navigation will appear here when you add resources to your EventCatalog.
|
|
1073
|
+
</p>
|
|
1074
|
+
</div>
|
|
1075
|
+
)}
|
|
1076
|
+
|
|
1077
|
+
{currentLevel.entries.length > 0 && renderEntries(currentLevel.entries)}
|
|
1050
1078
|
</nav>
|
|
1051
1079
|
</>
|
|
1052
1080
|
)}
|