@eventcatalog/core 2.65.0 → 3.0.0-beta.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.
- package/README.md +1 -26
- 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-NK6OYMRD.js → chunk-JB4YT5JY.js} +1 -1
- package/dist/{chunk-BMDTX5IN.js → chunk-TQ4HZREX.js} +1 -1
- package/dist/{chunk-IJRFYF4B.js → chunk-X4W4YC3U.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -21
- package/dist/eventcatalog.config.d.cts +10 -0
- package/dist/eventcatalog.config.d.ts +10 -0
- package/dist/eventcatalog.js +3 -20
- package/eventcatalog/src/components/CopyAsMarkdown.tsx +19 -1
- package/eventcatalog/src/components/FavoriteButton.tsx +54 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +386 -362
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +166 -518
- package/eventcatalog/src/components/Header.astro +48 -23
- package/eventcatalog/src/components/Lists/VersionList.astro +2 -2
- package/eventcatalog/src/components/MDX/Design/Design.astro +4 -1
- package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -1
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +3 -3
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +8 -2
- package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +37 -0
- package/eventcatalog/src/components/Search/Search.astro +48 -28
- package/eventcatalog/src/components/Search/SearchModal.tsx +393 -702
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +298 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/container.ts +66 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +101 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/flow.ts +29 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/message.ts +84 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +147 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +146 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1073 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +365 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +90 -0
- package/eventcatalog/src/components/SideNav/SideNav.astro +18 -28
- package/eventcatalog/src/content.config.ts +2 -0
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +10 -4
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +3 -3
- package/eventcatalog/src/layouts/DirectoryLayout.astro +2 -2
- package/eventcatalog/src/layouts/DiscoverLayout.astro +3 -3
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +85 -63
- package/eventcatalog/src/layouts/VisualiserLayout.astro +3 -3
- package/eventcatalog/src/pages/_index.astro +530 -110
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +64 -0
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +29 -0
- package/eventcatalog/src/pages/directory/[type]/_index.data.ts +4 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +3 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -5
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +362 -190
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +1 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +3 -27
- package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +2 -2
- package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +2 -2
- package/eventcatalog/src/pages/index.astro +14 -5
- package/eventcatalog/src/pages/nav-index.json.ts +30 -0
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +77 -0
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +90 -0
- package/eventcatalog/src/pages/schemas/{index.astro → explorer/index.astro} +3 -3
- package/eventcatalog/src/pages/studio.astro +3 -3
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +4 -3
- package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +2 -2
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +4 -3
- package/eventcatalog/src/stores/favorites-store.ts +83 -0
- package/eventcatalog/src/stores/sidebar-store.ts +8 -0
- package/eventcatalog/src/utils/collections/changelogs.ts +7 -4
- package/eventcatalog/src/utils/{channels.ts → collections/channels.ts} +81 -31
- package/eventcatalog/src/utils/collections/commands.ts +134 -0
- package/eventcatalog/src/utils/collections/containers.ts +44 -33
- package/eventcatalog/src/utils/collections/domains.ts +204 -62
- package/eventcatalog/src/utils/{entities.ts → collections/entities.ts} +44 -24
- package/eventcatalog/src/utils/collections/events.ts +136 -0
- package/eventcatalog/src/utils/collections/flows.ts +59 -25
- package/eventcatalog/src/utils/{messages.ts → collections/messages.ts} +13 -4
- package/eventcatalog/src/utils/{queries.ts → collections/queries.ts} +49 -28
- package/eventcatalog/src/utils/collections/services.ts +100 -68
- package/eventcatalog/src/utils/collections/teams.ts +94 -0
- package/eventcatalog/src/utils/collections/users.ts +122 -0
- package/eventcatalog/src/utils/collections/util.ts +57 -1
- package/eventcatalog/src/utils/feature.ts +3 -1
- package/eventcatalog/src/utils/{collections/file-diffs.ts → file-diffs.ts} +1 -1
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -0
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +16 -6
- package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +14 -10
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +36 -64
- package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +23 -19
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +36 -49
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +22 -18
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +4 -4
- package/eventcatalog/tailwind.config.mjs +14 -0
- package/eventcatalog/tsconfig.json +2 -1
- package/package.json +7 -4
- package/eventcatalog/public/logo_old.png +0 -0
- package/eventcatalog/src/components/DiscoverInsight.astro +0 -61
- package/eventcatalog/src/components/Grids/ServiceGrid.tsx +0 -534
- package/eventcatalog/src/components/Lists/CustomSideBarSectionList.astro +0 -55
- package/eventcatalog/src/components/Lists/ProtocolList.tsx +0 -74
- package/eventcatalog/src/components/Lists/RepositoryList.astro +0 -37
- package/eventcatalog/src/components/Lists/SpecificationsList.astro +0 -67
- package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +0 -204
- package/eventcatalog/src/components/SideBars/ContainerSideBar.astro +0 -180
- package/eventcatalog/src/components/SideBars/DomainSideBar.astro +0 -273
- package/eventcatalog/src/components/SideBars/EntitySideBar.astro +0 -139
- package/eventcatalog/src/components/SideBars/FlowSideBar.astro +0 -128
- package/eventcatalog/src/components/SideBars/MessageSideBar.astro +0 -248
- package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +0 -294
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/CollapsibleGroup.tsx +0 -46
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +0 -78
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +0 -83
- package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +0 -1250
- package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +0 -91
- package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +0 -201
- package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +0 -190
- package/eventcatalog/src/components/SideNav/TreeView/index.tsx +0 -94
- package/eventcatalog/src/components/TreeView/index.tsx +0 -328
- package/eventcatalog/src/components/TreeView/styles.module.css +0 -264
- package/eventcatalog/src/components/TreeView/useSlots.ts +0 -95
- package/eventcatalog/src/pages/architecture/[type]/index.astro +0 -14
- package/eventcatalog/src/pages/architecture/architecture.astro +0 -101
- package/eventcatalog/src/pages/architecture/docs/[type]/index.astro +0 -14
- package/eventcatalog/src/utils/commands.ts +0 -112
- package/eventcatalog/src/utils/events.ts +0 -108
- package/eventcatalog/src/utils/generators/index.ts +0 -10
- package/eventcatalog/src/utils/teams.ts +0 -72
- package/eventcatalog/src/utils/users.ts +0 -72
|
@@ -1,766 +1,457 @@
|
|
|
1
|
-
import React, { useState, useEffect,
|
|
1
|
+
import React, { Fragment, useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { Combobox, Dialog, Transition } from '@headlessui/react';
|
|
3
|
+
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
|
|
2
4
|
import {
|
|
3
|
-
MagnifyingGlassIcon,
|
|
4
|
-
QueueListIcon,
|
|
5
5
|
RectangleGroupIcon,
|
|
6
|
+
ServerIcon,
|
|
6
7
|
BoltIcon,
|
|
7
8
|
ChatBubbleLeftIcon,
|
|
8
|
-
|
|
9
|
+
MagnifyingGlassIcon as QueryIcon,
|
|
10
|
+
CubeIcon,
|
|
11
|
+
QueueListIcon,
|
|
9
12
|
UserGroupIcon,
|
|
10
13
|
UserIcon,
|
|
11
14
|
BookOpenIcon,
|
|
12
15
|
DocumentTextIcon,
|
|
13
|
-
|
|
16
|
+
ExclamationCircleIcon,
|
|
17
|
+
ArrowRightIcon,
|
|
18
|
+
ArrowUpIcon,
|
|
19
|
+
ArrowDownIcon,
|
|
20
|
+
ArrowUturnLeftIcon,
|
|
21
|
+
StarIcon,
|
|
22
|
+
Square2StackIcon,
|
|
14
23
|
} from '@heroicons/react/24/outline';
|
|
24
|
+
import { StarIcon as StarIconSolid, CircleStackIcon } from '@heroicons/react/24/solid';
|
|
25
|
+
import { useStore } from '@nanostores/react';
|
|
26
|
+
import { sidebarStore } from '../../stores/sidebar-store';
|
|
27
|
+
import { favoritesStore, toggleFavorite as toggleFavoriteAction } from '../../stores/favorites-store';
|
|
28
|
+
|
|
29
|
+
const typeIcons: any = {
|
|
30
|
+
Domain: RectangleGroupIcon,
|
|
31
|
+
Service: ServerIcon,
|
|
32
|
+
Event: BoltIcon,
|
|
33
|
+
Command: ChatBubbleLeftIcon,
|
|
34
|
+
Query: QueryIcon,
|
|
35
|
+
Entity: CubeIcon,
|
|
36
|
+
Channel: QueueListIcon,
|
|
37
|
+
Team: UserGroupIcon,
|
|
38
|
+
User: UserIcon,
|
|
39
|
+
Language: BookOpenIcon,
|
|
40
|
+
OpenAPI: DocumentTextIcon,
|
|
41
|
+
AsyncAPI: DocumentTextIcon,
|
|
42
|
+
Design: Square2StackIcon,
|
|
43
|
+
Container: CircleStackIcon,
|
|
44
|
+
default: DocumentTextIcon,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const typeColors: any = {
|
|
48
|
+
Domain: 'text-orange-500 bg-orange-50 ring-orange-200',
|
|
49
|
+
Service: 'text-pink-500 bg-pink-50 ring-pink-200',
|
|
50
|
+
Event: 'text-orange-500 bg-orange-50 ring-orange-200',
|
|
51
|
+
Command: 'text-blue-500 bg-blue-50 ring-blue-200',
|
|
52
|
+
Query: 'text-green-500 bg-green-50 ring-green-200',
|
|
53
|
+
Entity: 'text-purple-500 bg-purple-50 ring-purple-200',
|
|
54
|
+
Channel: 'text-indigo-500 bg-indigo-50 ring-indigo-200',
|
|
55
|
+
Team: 'text-teal-500 bg-teal-50 ring-teal-200',
|
|
56
|
+
User: 'text-cyan-500 bg-cyan-50 ring-cyan-200',
|
|
57
|
+
Language: 'text-amber-500 bg-amber-50 ring-amber-200',
|
|
58
|
+
OpenAPI: 'text-emerald-500 bg-emerald-50 ring-emerald-200',
|
|
59
|
+
AsyncAPI: 'text-violet-500 bg-violet-50 ring-violet-200',
|
|
60
|
+
Design: 'text-gray-500 bg-gray-50 ring-gray-200',
|
|
61
|
+
Container: 'text-indigo-500 bg-indigo-50 ring-indigo-200',
|
|
62
|
+
default: 'text-gray-500 bg-gray-50 ring-gray-200',
|
|
63
|
+
};
|
|
15
64
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
name: string;
|
|
19
|
-
type: string;
|
|
20
|
-
description: string;
|
|
21
|
-
url: string;
|
|
22
|
-
tags: string[];
|
|
65
|
+
function classNames(...classes: (string | boolean | undefined)[]) {
|
|
66
|
+
return classes.filter(Boolean).join(' ');
|
|
23
67
|
}
|
|
24
68
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
// Helper to construct URL from key if href is missing
|
|
70
|
+
const getUrlForItem = (node: any, key: string) => {
|
|
71
|
+
if (node.href) return node.href;
|
|
72
|
+
|
|
73
|
+
const parts = key.split(':');
|
|
74
|
+
if (parts.length < 2) return null; // Need at least type:id
|
|
75
|
+
|
|
76
|
+
const type = parts[0];
|
|
77
|
+
const id = parts[1];
|
|
78
|
+
const version = parts[2]; // May be undefined
|
|
79
|
+
|
|
80
|
+
// Skip list items and other special keys
|
|
81
|
+
if (type === 'list') return null;
|
|
82
|
+
|
|
83
|
+
// Only show items that have a version to avoid duplicates
|
|
84
|
+
if (!version) return null;
|
|
85
|
+
|
|
86
|
+
// Pluralize type for URL if needed
|
|
87
|
+
const pluralType = ['event', 'command', 'query', 'domain', 'service', 'flow', 'container'].includes(type) ? type + 's' : type; // users/teams already have href usually, but safe fallback
|
|
88
|
+
|
|
89
|
+
return `/docs/${pluralType}/${id}/${version}`;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default function SearchModal() {
|
|
93
|
+
const [query, setQuery] = useState('');
|
|
94
|
+
const [open, setOpen] = useState(false);
|
|
95
|
+
const [activeFilter, setActiveFilter] = useState('all');
|
|
96
|
+
const data = useStore(sidebarStore);
|
|
97
|
+
const favorites = useStore(favoritesStore);
|
|
98
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
47
99
|
|
|
48
|
-
return (
|
|
49
|
-
<a href={item.url} className="block group">
|
|
50
|
-
<div
|
|
51
|
-
className={`bg-gradient-to-br ${config.bg} to-white border rounded-lg p-3 hover:border-purple-300 hover:shadow-md transition-all duration-200 group-hover:shadow-lg ${config.border}`}
|
|
52
|
-
>
|
|
53
|
-
<div className="flex items-start justify-between mb-2">
|
|
54
|
-
<h3 className="text-base font-semibold text-gray-900 group-hover:text-purple-700 transition-colors">{item.name}</h3>
|
|
55
|
-
<span
|
|
56
|
-
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${config.badgeBg} ${config.text} ${config.border} border`}
|
|
57
|
-
>
|
|
58
|
-
<IconComponent className="h-3 w-3" />
|
|
59
|
-
{displayType}
|
|
60
|
-
</span>
|
|
61
|
-
</div>
|
|
62
|
-
{item.description && (
|
|
63
|
-
<div className="text-xs text-gray-500 mb-2 line-clamp-2 opacity-80">
|
|
64
|
-
{currentSearch.trim() ? (
|
|
65
|
-
<span dangerouslySetInnerHTML={{ __html: item.description }} />
|
|
66
|
-
) : (
|
|
67
|
-
<span>{item.description.replace(/<[^>]*>/g, '')}</span>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
)}
|
|
71
|
-
{item.tags.length > 0 && (
|
|
72
|
-
<div className="flex flex-wrap gap-1">
|
|
73
|
-
{item.tags.map((tag, index) => (
|
|
74
|
-
<span
|
|
75
|
-
key={index}
|
|
76
|
-
className="inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium bg-gray-100 text-gray-700"
|
|
77
|
-
>
|
|
78
|
-
{tag}
|
|
79
|
-
</span>
|
|
80
|
-
))}
|
|
81
|
-
</div>
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
</a>
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Memoized SearchResults component
|
|
89
|
-
const SearchResults = React.memo<{
|
|
90
|
-
results: SearchResult[];
|
|
91
|
-
typeConfig: any;
|
|
92
|
-
currentSearch: string;
|
|
93
|
-
}>(({ results, typeConfig, currentSearch }) => {
|
|
94
|
-
return (
|
|
95
|
-
<>
|
|
96
|
-
{results.map((item) => (
|
|
97
|
-
<SearchResultItem key={item.id} item={item} typeConfig={typeConfig} currentSearch={currentSearch} />
|
|
98
|
-
))}
|
|
99
|
-
</>
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const SearchModal: React.FC = () => {
|
|
104
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
105
|
-
const [pagefind, setPagefind] = useState<any>(null);
|
|
106
|
-
const [pagefindLoadError, setPagefindLoadError] = useState(false);
|
|
107
|
-
const [currentSearch, setCurrentSearch] = useState('');
|
|
108
|
-
const [currentFilter, setCurrentFilter] = useState('all');
|
|
109
|
-
const [allResults, setAllResults] = useState<SearchResult[]>([]);
|
|
110
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
111
|
-
const [exactMatch, setExactMatch] = useState(false);
|
|
112
|
-
|
|
113
|
-
// Listen for modal state changes from Astro component
|
|
114
100
|
useEffect(() => {
|
|
115
101
|
const handleModalToggle = (event: CustomEvent) => {
|
|
116
|
-
|
|
102
|
+
setOpen(event.detail.isOpen);
|
|
103
|
+
if (event.detail.isOpen) {
|
|
104
|
+
// Focus input when modal opens
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
inputRef.current?.focus();
|
|
107
|
+
}, 50);
|
|
108
|
+
}
|
|
117
109
|
};
|
|
118
110
|
|
|
119
111
|
window.addEventListener('searchModalToggle', handleModalToggle as EventListener);
|
|
120
112
|
return () => window.removeEventListener('searchModalToggle', handleModalToggle as EventListener);
|
|
121
113
|
}, []);
|
|
122
114
|
|
|
123
|
-
const
|
|
115
|
+
const closeModal = () => {
|
|
124
116
|
if ((window as any).searchModalState) {
|
|
125
117
|
(window as any).searchModalState.close();
|
|
118
|
+
} else {
|
|
119
|
+
setOpen(false);
|
|
126
120
|
}
|
|
127
121
|
};
|
|
128
122
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
border: 'border-blue-200',
|
|
158
|
-
icon: ChatBubbleLeftIcon,
|
|
159
|
-
},
|
|
160
|
-
queries: {
|
|
161
|
-
bg: 'bg-green-50/10',
|
|
162
|
-
badgeBg: 'bg-green-500/20',
|
|
163
|
-
text: 'text-green-800',
|
|
164
|
-
border: 'border-green-200',
|
|
165
|
-
icon: MagnifyingGlassIcon,
|
|
166
|
-
},
|
|
167
|
-
entities: {
|
|
168
|
-
bg: 'bg-purple-50/10',
|
|
169
|
-
badgeBg: 'bg-purple-500/20',
|
|
170
|
-
text: 'text-purple-800',
|
|
171
|
-
border: 'border-purple-200',
|
|
172
|
-
icon: CubeIcon,
|
|
173
|
-
},
|
|
174
|
-
channels: {
|
|
175
|
-
bg: 'bg-indigo-50/10',
|
|
176
|
-
badgeBg: 'bg-indigo-500/20',
|
|
177
|
-
text: 'text-indigo-800',
|
|
178
|
-
border: 'border-indigo-200',
|
|
179
|
-
icon: QueueListIcon,
|
|
180
|
-
},
|
|
181
|
-
teams: {
|
|
182
|
-
bg: 'bg-teal-50/10',
|
|
183
|
-
badgeBg: 'bg-teal-500/20',
|
|
184
|
-
text: 'text-teal-800',
|
|
185
|
-
border: 'border-teal-200',
|
|
186
|
-
icon: UserGroupIcon,
|
|
187
|
-
},
|
|
188
|
-
users: {
|
|
189
|
-
bg: 'bg-cyan-50/10',
|
|
190
|
-
badgeBg: 'bg-cyan-500/20',
|
|
191
|
-
text: 'text-cyan-800',
|
|
192
|
-
border: 'border-cyan-200',
|
|
193
|
-
icon: UserIcon,
|
|
194
|
-
},
|
|
195
|
-
language: {
|
|
196
|
-
bg: 'bg-amber-50/10',
|
|
197
|
-
badgeBg: 'bg-amber-500/20',
|
|
198
|
-
text: 'text-amber-800',
|
|
199
|
-
border: 'border-amber-200',
|
|
200
|
-
icon: BookOpenIcon,
|
|
201
|
-
},
|
|
202
|
-
openapi: {
|
|
203
|
-
bg: 'bg-emerald-50/10',
|
|
204
|
-
badgeBg: 'bg-emerald-500/20',
|
|
205
|
-
text: 'text-emerald-800',
|
|
206
|
-
border: 'border-emerald-200',
|
|
207
|
-
icon: DocumentTextIcon,
|
|
208
|
-
},
|
|
209
|
-
asyncapi: {
|
|
210
|
-
bg: 'bg-violet-50/10',
|
|
211
|
-
badgeBg: 'bg-violet-500/20',
|
|
212
|
-
text: 'text-violet-800',
|
|
213
|
-
border: 'border-violet-200',
|
|
214
|
-
icon: DocumentTextIcon,
|
|
215
|
-
},
|
|
216
|
-
other: {
|
|
217
|
-
bg: 'bg-gray-50/10',
|
|
218
|
-
badgeBg: 'bg-gray-500/20',
|
|
219
|
-
text: 'text-gray-800',
|
|
220
|
-
border: 'border-gray-200',
|
|
221
|
-
icon: DocumentTextIcon,
|
|
222
|
-
},
|
|
223
|
-
}),
|
|
224
|
-
[]
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
// Initialize Pagefind
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
if (typeof window !== 'undefined') {
|
|
230
|
-
const initPagefind = async () => {
|
|
231
|
-
try {
|
|
232
|
-
// Wait for Pagefind to be loaded by the Astro script
|
|
233
|
-
const waitForPagefind = () =>
|
|
234
|
-
new Promise<any>((resolve, reject) => {
|
|
235
|
-
if ((window as any).pagefind) {
|
|
236
|
-
resolve((window as any).pagefind);
|
|
237
|
-
} else {
|
|
238
|
-
const handler = () => {
|
|
239
|
-
window.removeEventListener('pagefindLoaded', handler);
|
|
240
|
-
resolve((window as any).pagefind);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Set a timeout to detect if pagefind fails to load
|
|
244
|
-
const timeout = setTimeout(() => {
|
|
245
|
-
window.removeEventListener('pagefindLoaded', handler);
|
|
246
|
-
reject(new Error('Pagefind failed to load - catalog may not be indexed'));
|
|
247
|
-
}, 2000); // 5 second timeout
|
|
248
|
-
|
|
249
|
-
window.addEventListener('pagefindLoaded', () => {
|
|
250
|
-
clearTimeout(timeout);
|
|
251
|
-
handler();
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const pagefindModule = await waitForPagefind();
|
|
257
|
-
await pagefindModule.init();
|
|
258
|
-
setPagefind(pagefindModule);
|
|
259
|
-
} catch (error) {
|
|
260
|
-
console.error('Failed to initialize Pagefind:', error);
|
|
261
|
-
setPagefindLoadError(true);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
initPagefind();
|
|
123
|
+
const items = useMemo(() => {
|
|
124
|
+
if (!data?.nodes) return [];
|
|
125
|
+
|
|
126
|
+
// Extract all items from nodes
|
|
127
|
+
const allItems = Object.entries(data.nodes)
|
|
128
|
+
.map(([key, node]) => {
|
|
129
|
+
const url = getUrlForItem(node, key);
|
|
130
|
+
if (!url) return null;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
id: url, // Use URL as unique ID
|
|
134
|
+
name: node.title,
|
|
135
|
+
url: url,
|
|
136
|
+
type: node.badge || 'Page',
|
|
137
|
+
key: key,
|
|
138
|
+
rawNode: node,
|
|
139
|
+
};
|
|
140
|
+
})
|
|
141
|
+
.filter((item): item is NonNullable<typeof item> => item !== null);
|
|
142
|
+
|
|
143
|
+
return allItems;
|
|
144
|
+
}, [data]);
|
|
145
|
+
|
|
146
|
+
// Get searchable items (items that match the query but not filtered by type yet)
|
|
147
|
+
const searchableItems = useMemo(() => {
|
|
148
|
+
if (query === '') {
|
|
149
|
+
// When no query, show all items for filter counts
|
|
150
|
+
return items;
|
|
266
151
|
}
|
|
267
|
-
}, []);
|
|
268
152
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (url.includes('/language/')) return 'language';
|
|
273
|
-
// Check for spec types after language but before other types since they can be nested
|
|
274
|
-
if (url.includes('/spec/')) return 'openapi';
|
|
275
|
-
if (url.includes('/asyncapi')) return 'asyncapi';
|
|
276
|
-
if (url.includes('/domains/')) return 'domains';
|
|
277
|
-
if (url.includes('/services/')) return 'services';
|
|
278
|
-
if (url.includes('/events/')) return 'events';
|
|
279
|
-
if (url.includes('/commands/')) return 'commands';
|
|
280
|
-
if (url.includes('/queries/')) return 'queries';
|
|
281
|
-
if (url.includes('/entities/')) return 'entities';
|
|
282
|
-
if (url.includes('/channels/')) return 'channels';
|
|
283
|
-
if (url.includes('/teams/')) return 'teams';
|
|
284
|
-
if (url.includes('/users/')) return 'users';
|
|
285
|
-
return 'other';
|
|
286
|
-
}, []);
|
|
153
|
+
const lowerQuery = query.toLowerCase();
|
|
154
|
+
return items.filter((item) => item.name.toLowerCase().includes(lowerQuery));
|
|
155
|
+
}, [items, query]);
|
|
287
156
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
157
|
+
const filters = useMemo(() => {
|
|
158
|
+
// Calculate counts based on current search results (searchableItems)
|
|
159
|
+
if (!searchableItems.length && query !== '') {
|
|
160
|
+
// If searching and no results, still show filters but with 0 counts
|
|
161
|
+
return [{ id: 'all', name: 'All (0)' }];
|
|
162
|
+
}
|
|
294
163
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
const search = await pagefind.debouncedSearch(searchTerm);
|
|
299
|
-
if (!search || !search.results) {
|
|
300
|
-
return [];
|
|
301
|
-
}
|
|
302
|
-
const processedResults: SearchResult[] = [];
|
|
303
|
-
|
|
304
|
-
for (const result of search.results) {
|
|
305
|
-
const data = await result.data();
|
|
306
|
-
const type = getTypeFromUrl(data.url);
|
|
307
|
-
|
|
308
|
-
// Clean the title by removing any "Type | " prefix if it exists
|
|
309
|
-
let cleanTitle = data.meta?.title || 'Untitled';
|
|
310
|
-
|
|
311
|
-
// Use regex for more efficient prefix removal
|
|
312
|
-
cleanTitle = cleanTitle.replace(
|
|
313
|
-
/^(Domains?|Services?|Events?|Commands?|Queries?|Entities?|Channels?|Teams?|Users?|Language) \| /,
|
|
314
|
-
''
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
processedResults.push({
|
|
318
|
-
id: result.id,
|
|
319
|
-
name: cleanTitle,
|
|
320
|
-
type: type,
|
|
321
|
-
description: data.excerpt || '',
|
|
322
|
-
url: data.url,
|
|
323
|
-
tags: data.meta?.tags ? data.meta.tags.split(',').map((tag: string) => tag.trim()) : [],
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return processedResults;
|
|
328
|
-
} catch (error) {
|
|
329
|
-
console.error('Search error:', error);
|
|
330
|
-
return [];
|
|
331
|
-
} finally {
|
|
332
|
-
setIsLoading(false);
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
[pagefind]
|
|
336
|
-
);
|
|
164
|
+
const itemsToCount = query === '' ? items : searchableItems;
|
|
337
165
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
166
|
+
const counts: Record<string, number> = {
|
|
167
|
+
all: itemsToCount.length,
|
|
168
|
+
Domain: 0,
|
|
169
|
+
Service: 0,
|
|
170
|
+
Message: 0,
|
|
171
|
+
Team: 0,
|
|
172
|
+
Container: 0,
|
|
173
|
+
Design: 0,
|
|
174
|
+
};
|
|
342
175
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
176
|
+
itemsToCount.forEach((item) => {
|
|
177
|
+
// Count specific types
|
|
178
|
+
if (counts[item.type] !== undefined) {
|
|
179
|
+
counts[item.type]++;
|
|
346
180
|
}
|
|
347
181
|
|
|
348
|
-
//
|
|
349
|
-
if (
|
|
350
|
-
|
|
182
|
+
// Group counts
|
|
183
|
+
if (['Event', 'Command', 'Query'].includes(item.type)) {
|
|
184
|
+
counts.Message++;
|
|
185
|
+
}
|
|
186
|
+
if (['Team', 'User'].includes(item.type)) {
|
|
187
|
+
counts.Team++;
|
|
351
188
|
}
|
|
189
|
+
});
|
|
352
190
|
|
|
353
|
-
|
|
354
|
-
},
|
|
355
|
-
[exactMatch, currentSearch]
|
|
356
|
-
);
|
|
191
|
+
const dynamicFilters = [{ id: 'all', name: `All (${counts.all})` }];
|
|
357
192
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
}, [currentSearch, performSearch]);
|
|
193
|
+
// Only show filters that have results when searching
|
|
194
|
+
if (counts.Domain > 0) dynamicFilters.push({ id: 'Domain', name: `Domains (${counts.Domain})` });
|
|
195
|
+
if (counts.Service > 0) dynamicFilters.push({ id: 'Service', name: `Services (${counts.Service})` });
|
|
196
|
+
if (counts.Message > 0) dynamicFilters.push({ id: 'Message', name: `Messages (${counts.Message})` });
|
|
197
|
+
if (counts.Container > 0) dynamicFilters.push({ id: 'Container', name: `Containers (${counts.Container})` });
|
|
198
|
+
if (counts.Design > 0) dynamicFilters.push({ id: 'Design', name: `Designs (${counts.Design})` });
|
|
199
|
+
if (counts.Team > 0) dynamicFilters.push({ id: 'Team', name: `Teams & Users (${counts.Team})` });
|
|
367
200
|
|
|
368
|
-
|
|
201
|
+
return dynamicFilters;
|
|
202
|
+
}, [searchableItems, items, query]);
|
|
203
|
+
|
|
204
|
+
// Reset active filter if it no longer has results
|
|
369
205
|
useEffect(() => {
|
|
370
|
-
if (!
|
|
371
|
-
|
|
372
|
-
return;
|
|
206
|
+
if (activeFilter !== 'all' && !filters.some((f) => f.id === activeFilter)) {
|
|
207
|
+
setActiveFilter('all');
|
|
373
208
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
// Handle filter change
|
|
388
|
-
const handleFilterChange = (filter: string) => {
|
|
389
|
-
setCurrentFilter(filter);
|
|
209
|
+
}, [filters, activeFilter]);
|
|
210
|
+
|
|
211
|
+
const handleToggleFavorite = (e: React.MouseEvent, item: any) => {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
e.stopPropagation();
|
|
214
|
+
|
|
215
|
+
toggleFavoriteAction({
|
|
216
|
+
nodeKey: item.key,
|
|
217
|
+
path: [], // Path is not easily available here, but Sidebar rebuilds it
|
|
218
|
+
title: item.name,
|
|
219
|
+
badge: item.rawNode.badge,
|
|
220
|
+
href: item.url,
|
|
221
|
+
});
|
|
390
222
|
};
|
|
391
223
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const counts = getFilterCounts;
|
|
417
|
-
|
|
418
|
-
// Handle escape key
|
|
419
|
-
useEffect(() => {
|
|
420
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
421
|
-
if (e.key === 'Escape') {
|
|
422
|
-
onClose();
|
|
224
|
+
const filteredItems = useMemo(() => {
|
|
225
|
+
if (query === '') {
|
|
226
|
+
// Show favorites when search is empty
|
|
227
|
+
if (favorites.length > 0) {
|
|
228
|
+
return favorites
|
|
229
|
+
.slice(0, 5)
|
|
230
|
+
.map((fav) => {
|
|
231
|
+
const node = data?.nodes[fav.nodeKey];
|
|
232
|
+
if (!node) return null;
|
|
233
|
+
const url = getUrlForItem(node, fav.nodeKey);
|
|
234
|
+
if (!url) return null;
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
id: url,
|
|
238
|
+
name: fav.title,
|
|
239
|
+
url: url,
|
|
240
|
+
type: fav.badge || 'Page',
|
|
241
|
+
key: fav.nodeKey,
|
|
242
|
+
rawNode: node,
|
|
243
|
+
isFavorite: true,
|
|
244
|
+
};
|
|
245
|
+
})
|
|
246
|
+
.filter((item): item is NonNullable<typeof item> => item !== null);
|
|
423
247
|
}
|
|
424
|
-
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
425
250
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
251
|
+
// Start with searchable items (already filtered by query)
|
|
252
|
+
let result = searchableItems;
|
|
253
|
+
|
|
254
|
+
// Apply type filter
|
|
255
|
+
if (activeFilter !== 'all') {
|
|
256
|
+
if (activeFilter === 'Message') {
|
|
257
|
+
result = result.filter((item) => ['Event', 'Command', 'Query'].includes(item.type));
|
|
258
|
+
} else if (activeFilter === 'Team') {
|
|
259
|
+
result = result.filter((item) => ['Team', 'User'].includes(item.type));
|
|
260
|
+
} else {
|
|
261
|
+
result = result.filter((item) => item.type === activeFilter);
|
|
262
|
+
}
|
|
429
263
|
}
|
|
430
|
-
}, [isOpen, onClose]);
|
|
431
264
|
|
|
432
|
-
|
|
265
|
+
return result.slice(0, 50); // Limit results for performance
|
|
266
|
+
}, [searchableItems, query, activeFilter, favorites, data]);
|
|
433
267
|
|
|
434
268
|
return (
|
|
435
|
-
<
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
269
|
+
<Transition.Root
|
|
270
|
+
show={open}
|
|
271
|
+
as={Fragment}
|
|
272
|
+
afterLeave={() => {
|
|
273
|
+
setQuery('');
|
|
274
|
+
setActiveFilter('all');
|
|
275
|
+
}}
|
|
276
|
+
appear
|
|
277
|
+
>
|
|
278
|
+
<Dialog as="div" className="relative z-50" onClose={closeModal}>
|
|
279
|
+
<Transition.Child
|
|
280
|
+
as={Fragment}
|
|
281
|
+
enter="ease-out duration-300"
|
|
282
|
+
enterFrom="opacity-0"
|
|
283
|
+
enterTo="opacity-100"
|
|
284
|
+
leave="ease-in duration-200"
|
|
285
|
+
leaveFrom="opacity-100"
|
|
286
|
+
leaveTo="opacity-0"
|
|
287
|
+
>
|
|
288
|
+
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity backdrop-blur-sm" />
|
|
289
|
+
</Transition.Child>
|
|
290
|
+
|
|
291
|
+
<div className="fixed inset-0 z-10 w-screen overflow-y-auto p-4 sm:p-6 md:p-20">
|
|
292
|
+
<Transition.Child
|
|
293
|
+
as={Fragment}
|
|
294
|
+
enter="ease-out duration-300"
|
|
295
|
+
enterFrom="opacity-0 scale-95"
|
|
296
|
+
enterTo="opacity-100 scale-100"
|
|
297
|
+
leave="ease-in duration-200"
|
|
298
|
+
leaveFrom="opacity-100 scale-100"
|
|
299
|
+
leaveTo="opacity-0 scale-95"
|
|
454
300
|
>
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
<code className="text-green-400 font-mono text-sm">npm run build</code>
|
|
476
|
-
</div>
|
|
477
|
-
<p className="text-sm text-gray-600">This will generate your catalog and create the search index</p>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<div className="flex items-start text-left bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
481
|
-
<svg
|
|
482
|
-
className="h-5 w-5 text-blue-600 mr-3 mt-0.5 flex-shrink-0"
|
|
483
|
-
fill="none"
|
|
484
|
-
stroke="currentColor"
|
|
485
|
-
viewBox="0 0 24 24"
|
|
486
|
-
>
|
|
487
|
-
<path
|
|
488
|
-
strokeLinecap="round"
|
|
489
|
-
strokeLinejoin="round"
|
|
490
|
-
strokeWidth="2"
|
|
491
|
-
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
492
|
-
/>
|
|
493
|
-
</svg>
|
|
494
|
-
<div>
|
|
495
|
-
<h4 className="font-medium text-blue-900 mb-1">Need to update search results?</h4>
|
|
496
|
-
<p className="text-sm text-blue-700">
|
|
497
|
-
Run <code className="bg-blue-100 px-1 py-0.5 rounded text-xs font-mono">npm run build</code> again after
|
|
498
|
-
making changes to your catalog content.
|
|
499
|
-
</p>
|
|
500
|
-
</div>
|
|
501
|
-
</div>
|
|
502
|
-
</div>
|
|
503
|
-
</div>
|
|
504
|
-
) : (
|
|
505
|
-
<>
|
|
506
|
-
{/* Search Input */}
|
|
507
|
-
<div className="relative px-6 pt-4 pb-2">
|
|
508
|
-
<MagnifyingGlassIcon className="pointer-events-none absolute left-10 top-[25px] h-5 w-5 text-gray-400" />
|
|
509
|
-
<input
|
|
510
|
-
type="text"
|
|
511
|
-
placeholder="Search for domains, services, events..."
|
|
512
|
-
className="w-full border border-gray-200 rounded-lg bg-white pl-12 pr-4 py-2 text-gray-900 placeholder:text-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm"
|
|
513
|
-
value={currentSearch}
|
|
514
|
-
onChange={handleSearchChange}
|
|
301
|
+
<Dialog.Panel className="mx-auto max-w-2xl transform divide-y divide-gray-100 overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
|
|
302
|
+
<Combobox
|
|
303
|
+
onChange={(item: any) => {
|
|
304
|
+
if (item?.url) {
|
|
305
|
+
window.location.href = item.url;
|
|
306
|
+
closeModal();
|
|
307
|
+
}
|
|
308
|
+
}}
|
|
309
|
+
>
|
|
310
|
+
<div className="relative border-b border-gray-100">
|
|
311
|
+
<MagnifyingGlassIcon
|
|
312
|
+
className="pointer-events-none absolute left-4 top-3.5 h-5 w-5 text-gray-400"
|
|
313
|
+
aria-hidden="true"
|
|
314
|
+
/>
|
|
315
|
+
<Combobox.Input
|
|
316
|
+
ref={inputRef}
|
|
317
|
+
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm focus:outline-none"
|
|
318
|
+
placeholder="Search..."
|
|
319
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
320
|
+
value={query}
|
|
515
321
|
autoFocus
|
|
322
|
+
autoComplete="off"
|
|
516
323
|
/>
|
|
517
324
|
</div>
|
|
518
325
|
|
|
519
|
-
{/*
|
|
520
|
-
<div className="flex
|
|
521
|
-
{
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
</button>
|
|
541
|
-
</div>
|
|
542
|
-
</div>
|
|
543
|
-
|
|
544
|
-
{/* Resources Section */}
|
|
545
|
-
<div className="mb-4">
|
|
546
|
-
<h3 className="text-xs font-bold text-gray-600 mb-2">Resources</h3>
|
|
547
|
-
<div className="space-y-1">
|
|
548
|
-
{Object.entries({
|
|
549
|
-
domains: 'Domains',
|
|
550
|
-
services: 'Services',
|
|
551
|
-
entities: 'Entities',
|
|
552
|
-
language: 'Ubiquitous Language',
|
|
553
|
-
}).map(([key, label]) => {
|
|
554
|
-
const config = typeConfig[key as keyof typeof typeConfig];
|
|
555
|
-
const IconComponent = config?.icon;
|
|
556
|
-
|
|
557
|
-
return (
|
|
558
|
-
<button
|
|
559
|
-
key={key}
|
|
560
|
-
className={`w-full px-2 py-1 text-xs rounded-md transition-colors ${
|
|
561
|
-
currentFilter === key
|
|
562
|
-
? 'bg-purple-200 text-purple-900 font-semibold'
|
|
563
|
-
: 'hover:bg-purple-100 text-gray-700 hover:text-purple-800'
|
|
564
|
-
}`}
|
|
565
|
-
onClick={() => handleFilterChange(key)}
|
|
566
|
-
>
|
|
567
|
-
<div className="flex items-center justify-between">
|
|
568
|
-
<div className="flex items-center gap-1.5">
|
|
569
|
-
{IconComponent && <IconComponent className="h-3 w-3" />}
|
|
570
|
-
<span className="font-thin">{label}</span>
|
|
571
|
-
</div>
|
|
572
|
-
<span className="text-xs text-gray-700 font-thin">{counts[key as keyof typeof counts]}</span>
|
|
573
|
-
</div>
|
|
574
|
-
</button>
|
|
575
|
-
);
|
|
576
|
-
})}
|
|
326
|
+
{/* Filter Tabs */}
|
|
327
|
+
<div className="flex items-center gap-2 px-4 py-3 overflow-x-auto no-scrollbar border-b border-gray-100">
|
|
328
|
+
{filters.map((tab) => (
|
|
329
|
+
<button
|
|
330
|
+
key={tab.id}
|
|
331
|
+
onClick={() => setActiveFilter(tab.id)}
|
|
332
|
+
className={classNames(
|
|
333
|
+
'px-3 py-1 text-xs font-medium rounded-full transition-colors whitespace-nowrap',
|
|
334
|
+
activeFilter === tab.id ? 'bg-purple-100 text-purple-700' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
|
335
|
+
)}
|
|
336
|
+
>
|
|
337
|
+
{tab.name}
|
|
338
|
+
</button>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
{filteredItems.length > 0 && (
|
|
343
|
+
<>
|
|
344
|
+
{query === '' && favorites.length > 0 && (
|
|
345
|
+
<div className="px-6 pt-3 pb-2">
|
|
346
|
+
<p className="text-xs text-gray-500">Favourites</p>
|
|
577
347
|
</div>
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
<div className="flex items-center justify-between">
|
|
604
|
-
<div className="flex items-center gap-1.5">
|
|
605
|
-
{IconComponent && <IconComponent className="h-3 w-3" />}
|
|
606
|
-
<span className="font-thin">{label}</span>
|
|
348
|
+
)}
|
|
349
|
+
<Combobox.Options static className="max-h-96 scroll-py-3 overflow-y-auto p-3">
|
|
350
|
+
{filteredItems.map((item) => {
|
|
351
|
+
const Icon = typeIcons[item.type] || typeIcons.default;
|
|
352
|
+
const colors = typeColors[item.type] || typeColors.default;
|
|
353
|
+
|
|
354
|
+
const isFavorite = favorites.some((fav) => fav.nodeKey === item.key);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<Combobox.Option
|
|
358
|
+
key={item.id}
|
|
359
|
+
value={item}
|
|
360
|
+
className={({ active }) =>
|
|
361
|
+
classNames('flex cursor-default select-none rounded-xl p-3 group', active && 'bg-gray-100')
|
|
362
|
+
}
|
|
363
|
+
>
|
|
364
|
+
{({ active }) => (
|
|
365
|
+
<>
|
|
366
|
+
<div
|
|
367
|
+
className={classNames(
|
|
368
|
+
'flex h-10 w-10 flex-none items-center justify-center rounded-lg ring-1 ring-inset',
|
|
369
|
+
colors
|
|
370
|
+
)}
|
|
371
|
+
>
|
|
372
|
+
<Icon className="h-6 w-6" aria-hidden="true" />
|
|
607
373
|
</div>
|
|
608
|
-
<
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const config = typeConfig[key as keyof typeof typeConfig];
|
|
625
|
-
const IconComponent = config?.icon;
|
|
626
|
-
|
|
627
|
-
return (
|
|
628
|
-
<button
|
|
629
|
-
key={key}
|
|
630
|
-
className={`w-full px-2 py-1 text-xs rounded-md transition-colors ${
|
|
631
|
-
currentFilter === key
|
|
632
|
-
? 'bg-purple-200 text-purple-900 font-semibold'
|
|
633
|
-
: 'hover:bg-purple-100 text-gray-700 hover:text-purple-800'
|
|
634
|
-
}`}
|
|
635
|
-
onClick={() => handleFilterChange(key)}
|
|
636
|
-
>
|
|
637
|
-
<div className="flex items-center justify-between">
|
|
638
|
-
<div className="flex items-center gap-1.5">
|
|
639
|
-
{IconComponent && <IconComponent className="h-3 w-3" />}
|
|
640
|
-
<span className="font-thin">{label}</span>
|
|
374
|
+
<div className="ml-4 flex-auto min-w-0">
|
|
375
|
+
<p className={classNames('text-sm font-medium', active ? 'text-gray-900' : 'text-gray-700')}>
|
|
376
|
+
{item.name}
|
|
377
|
+
</p>
|
|
378
|
+
<div className="flex items-start gap-2">
|
|
379
|
+
<p
|
|
380
|
+
className={classNames('text-sm flex-shrink-0', active ? 'text-gray-700' : 'text-gray-500')}
|
|
381
|
+
>
|
|
382
|
+
{item.type}
|
|
383
|
+
</p>
|
|
384
|
+
{item.rawNode.summary && (
|
|
385
|
+
<p className={classNames('text-xs truncate', active ? 'text-gray-600' : 'text-gray-400')}>
|
|
386
|
+
• {item.rawNode.summary}
|
|
387
|
+
</p>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
641
390
|
</div>
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const IconComponent = config.icon;
|
|
660
|
-
|
|
661
|
-
return (
|
|
662
|
-
<button
|
|
663
|
-
key={key}
|
|
664
|
-
className={`w-full px-2 py-1 text-xs rounded-md transition-colors ${
|
|
665
|
-
currentFilter === key
|
|
666
|
-
? 'bg-purple-200 text-purple-900 font-semibold'
|
|
667
|
-
: 'hover:bg-purple-100 text-gray-700 hover:text-purple-800'
|
|
668
|
-
}`}
|
|
669
|
-
onClick={() => handleFilterChange(key)}
|
|
670
|
-
>
|
|
671
|
-
<div className="flex items-center justify-between">
|
|
672
|
-
<div className="flex items-center gap-1.5">
|
|
673
|
-
<IconComponent className="h-3 w-3" />
|
|
674
|
-
<span className="font-thin">{label}</span>
|
|
391
|
+
<div className="flex items-center">
|
|
392
|
+
<button
|
|
393
|
+
onClick={(e) => handleToggleFavorite(e, item)}
|
|
394
|
+
onMouseDown={(e) => {
|
|
395
|
+
e.preventDefault();
|
|
396
|
+
e.stopPropagation();
|
|
397
|
+
}}
|
|
398
|
+
className={classNames(
|
|
399
|
+
'p-1 rounded-md transition-colors mr-2',
|
|
400
|
+
isFavorite
|
|
401
|
+
? 'text-amber-400 hover:text-amber-500'
|
|
402
|
+
: 'text-gray-300 opacity-0 group-hover:opacity-100 hover:text-amber-400'
|
|
403
|
+
)}
|
|
404
|
+
>
|
|
405
|
+
{isFavorite ? <StarIconSolid className="h-5 w-5" /> : <StarIcon className="h-5 w-5" />}
|
|
406
|
+
</button>
|
|
407
|
+
{active && <ArrowRightIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />}
|
|
675
408
|
</div>
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
409
|
+
</>
|
|
410
|
+
)}
|
|
411
|
+
</Combobox.Option>
|
|
412
|
+
);
|
|
413
|
+
})}
|
|
414
|
+
</Combobox.Options>
|
|
415
|
+
</>
|
|
416
|
+
)}
|
|
417
|
+
|
|
418
|
+
{query !== '' && filteredItems.length === 0 && (
|
|
419
|
+
<div className="py-14 px-6 text-center text-sm sm:px-14">
|
|
420
|
+
<ExclamationCircleIcon type="outline" name="exclamation-circle" className="mx-auto h-6 w-6 text-gray-400" />
|
|
421
|
+
<p className="mt-4 font-semibold text-gray-900">No results found</p>
|
|
422
|
+
<p className="mt-2 text-gray-500">No components found for this search term. Please try again.</p>
|
|
683
423
|
</div>
|
|
424
|
+
)}
|
|
684
425
|
|
|
685
|
-
|
|
686
|
-
<div className="
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
{isLoading && <span className="ml-2">Loading...</span>}
|
|
693
|
-
</div>
|
|
694
|
-
|
|
695
|
-
{/* Exact Match Checkbox */}
|
|
696
|
-
<div className="flex items-center">
|
|
697
|
-
<input
|
|
698
|
-
id="exact-match-results"
|
|
699
|
-
type="checkbox"
|
|
700
|
-
checked={exactMatch}
|
|
701
|
-
onChange={(e) => setExactMatch(e.target.checked)}
|
|
702
|
-
className="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded"
|
|
703
|
-
/>
|
|
704
|
-
<label htmlFor="exact-match-results" className="ml-2 text-sm text-gray-600">
|
|
705
|
-
Exact match in title
|
|
706
|
-
</label>
|
|
707
|
-
</div>
|
|
708
|
-
</div>
|
|
709
|
-
)}
|
|
426
|
+
{query === '' && filteredItems.length === 0 && (
|
|
427
|
+
<div className="py-14 px-6 text-center text-sm sm:px-14">
|
|
428
|
+
<MagnifyingGlassIcon className="mx-auto h-6 w-6 text-gray-400" />
|
|
429
|
+
<p className="mt-4 font-semibold text-gray-900">Search for anything</p>
|
|
430
|
+
<p className="mt-2 text-gray-500">Search for domains, services, events, commands, queries and more.</p>
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
710
433
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
// Show when search term exists but no results and not loading - centered
|
|
726
|
-
<div className="h-full flex items-center justify-center">
|
|
727
|
-
<div className="text-center">
|
|
728
|
-
<MagnifyingGlassIcon className="mx-auto h-8 w-8 text-gray-300" />
|
|
729
|
-
<h3 className="mt-2 text-sm font-bold text-gray-900">No results found</h3>
|
|
730
|
-
<p className="mt-1 text-sm text-gray-500 font-thin">
|
|
731
|
-
No results found for "<span className="font-medium">{currentSearch}</span>".
|
|
732
|
-
</p>
|
|
733
|
-
</div>
|
|
734
|
-
</div>
|
|
735
|
-
) : isLoading ? (
|
|
736
|
-
// Show loading state - centered
|
|
737
|
-
<div className="h-full flex items-center justify-center">
|
|
738
|
-
<div className="text-center">
|
|
739
|
-
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto"></div>
|
|
740
|
-
<h3 className="mt-4 text-sm font-medium text-gray-900">Searching...</h3>
|
|
741
|
-
<p className="mt-2 text-sm text-gray-500 font-thin">
|
|
742
|
-
Finding results for "<span className="font-medium">{currentSearch}</span>"
|
|
743
|
-
</p>
|
|
744
|
-
</div>
|
|
745
|
-
</div>
|
|
746
|
-
) : (
|
|
747
|
-
// Show results in a grid with padding
|
|
748
|
-
<div className="p-4">
|
|
749
|
-
<div className="search-results grid grid-cols-1 lg:grid-cols-2 gap-3">
|
|
750
|
-
<SearchResults results={filteredResults} typeConfig={typeConfig} currentSearch={currentSearch} />
|
|
751
|
-
</div>
|
|
752
|
-
</div>
|
|
753
|
-
)}
|
|
754
|
-
</div>
|
|
434
|
+
{/* Footer */}
|
|
435
|
+
<div className="flex flex-wrap items-center bg-gray-50 py-2.5 px-4 text-xs text-gray-500 border-t border-gray-100">
|
|
436
|
+
<div className="flex items-center mr-4">
|
|
437
|
+
<ArrowUturnLeftIcon className="h-3 w-3 mr-1" />
|
|
438
|
+
to select
|
|
439
|
+
</div>
|
|
440
|
+
<div className="flex items-center mr-4">
|
|
441
|
+
<ArrowDownIcon className="h-3 w-3 mr-1" />
|
|
442
|
+
<ArrowUpIcon className="h-3 w-3 mr-1" />
|
|
443
|
+
to navigate
|
|
444
|
+
</div>
|
|
445
|
+
<div className="flex items-center">
|
|
446
|
+
<span className="mr-1">esc</span>
|
|
447
|
+
to close
|
|
755
448
|
</div>
|
|
756
449
|
</div>
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
</
|
|
450
|
+
</Combobox>
|
|
451
|
+
</Dialog.Panel>
|
|
452
|
+
</Transition.Child>
|
|
760
453
|
</div>
|
|
761
|
-
</
|
|
762
|
-
</
|
|
454
|
+
</Dialog>
|
|
455
|
+
</Transition.Root>
|
|
763
456
|
);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
export default SearchModal;
|
|
457
|
+
}
|