@eventcatalog/core 2.30.7 → 2.31.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 +3 -2
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +9 -3
- package/dist/analytics/log-build.d.cts +4 -1
- package/dist/analytics/log-build.d.ts +4 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-SUJLBNZK.js → chunk-4S3UNXH2.js} +1 -1
- package/dist/{chunk-HINNLTBH.js → chunk-D7LV5JLL.js} +9 -3
- package/dist/{chunk-EFSBN3ZZ.js → chunk-I6OFOESY.js} +1 -1
- package/dist/{chunk-XMDPVKIJ.js → chunk-NJGR7XUU.js} +44 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +74 -14
- package/dist/eventcatalog.config.d.cts +28 -0
- package/dist/eventcatalog.config.d.ts +28 -0
- package/dist/eventcatalog.js +29 -16
- package/dist/features.cjs +46 -2
- package/dist/features.d.cts +2 -1
- package/dist/features.d.ts +2 -1
- package/dist/features.js +5 -3
- package/eventcatalog/public/images/custom-docs-placeholder.png +0 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Custom.tsx +0 -2
- package/eventcatalog/src/components/MDX/Steps/Step.astro +1 -1
- package/eventcatalog/src/components/MDX/Steps/Steps.astro +15 -0
- package/eventcatalog/src/components/SideBars/FlowSideBar.astro +75 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NestedItem.tsx +183 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/components/NoResultsFound.tsx +21 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/index.tsx +250 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav/types.ts +29 -0
- package/eventcatalog/src/components/SideNav/CustomDocsNav.astro +9 -0
- package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +2 -2
- package/eventcatalog/src/content.config.ts +15 -24
- package/eventcatalog/src/enterprise/collections/custom-pages.ts +19 -0
- package/eventcatalog/src/enterprise/custom-documentation/collection.ts +16 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper.tsx +11 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NestedItem.tsx +183 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/components/NoResultsFound.tsx +21 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/index.tsx +250 -0
- package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/types.ts +29 -0
- package/eventcatalog/src/enterprise/custom-documentation/pages/index.astro +389 -0
- package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +118 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +58 -9
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/index.astro +23 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +13 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +134 -17
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/index.astro +21 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/language.astro +1 -1
- package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +260 -0
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +5 -3
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +9 -4
- package/eventcatalog/src/pages/pro/index.astro +272 -0
- package/eventcatalog/src/shared-collections.ts +25 -0
- package/eventcatalog/src/types/index.ts +1 -1
- package/eventcatalog/src/utils/eventcatalog-config/catalog.ts +12 -1
- package/eventcatalog/src/utils/feature.ts +5 -0
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
- package/package.json +1 -1
|
@@ -38,8 +38,6 @@ function classNames(...classes: any) {
|
|
|
38
38
|
export default function UserNode({ data, sourcePosition, targetPosition }: any) {
|
|
39
39
|
const { mode, step, custom: customProps } = data as Data;
|
|
40
40
|
|
|
41
|
-
console.log('step', customProps);
|
|
42
|
-
|
|
43
41
|
const {
|
|
44
42
|
color = 'blue',
|
|
45
43
|
title = 'Custom',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { title } = Astro.props;
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
<li class="mb-8 ml-6 w-full xl:max-w-[
|
|
5
|
+
<li class="mb-8 ml-6 w-full xl:max-w-[90%] step">
|
|
6
6
|
<div class="flex items-center mb-4">
|
|
7
7
|
<span class="step-number absolute flex items-center justify-center w-6 h-6 bg-primary/80 rounded-md -left-3 text-white">
|
|
8
8
|
<div></div>
|
|
@@ -25,3 +25,18 @@ const data = splitByLi(html);
|
|
|
25
25
|
{data.map((item: any) => <Fragment set:html={item} />)}
|
|
26
26
|
</ol>
|
|
27
27
|
</div>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
function applyStepNumbers() {
|
|
31
|
+
document.querySelectorAll('li[data-step]').forEach((li) => {
|
|
32
|
+
const stepNumber = li.getAttribute('data-step');
|
|
33
|
+
const stepSpan = li.querySelector('.step-number div');
|
|
34
|
+
if (stepSpan) {
|
|
35
|
+
stepSpan.textContent = stepNumber;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
document.addEventListener('DOMContentLoaded', applyStepNumbers);
|
|
41
|
+
document.addEventListener('astro:page-load', applyStepNumbers);
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
import OwnersList from '@components/Lists/OwnersList';
|
|
3
|
+
import VersionList from '@components/Lists/VersionList.astro';
|
|
4
|
+
import { buildUrl } from '@utils/url-builder';
|
|
5
|
+
import { getOwner } from '@utils/collections/owners';
|
|
6
|
+
import type { CollectionEntry } from 'astro:content';
|
|
7
|
+
import { ScrollText, Workflow, RssIcon } from 'lucide-react';
|
|
8
|
+
import config from '@config';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
flow: CollectionEntry<'flows'>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { flow } = Astro.props;
|
|
15
|
+
|
|
16
|
+
const ownersRaw = flow.data?.owners || [];
|
|
17
|
+
const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
|
|
18
|
+
const filteredOwners = owners.filter((o) => o !== undefined);
|
|
19
|
+
|
|
20
|
+
const ownersList = filteredOwners.map((o) => ({
|
|
21
|
+
label: o.data.name,
|
|
22
|
+
type: o.collection,
|
|
23
|
+
badge: o.collection === 'users' ? o.data.role : 'Team',
|
|
24
|
+
avatarUrl: o.collection === 'users' ? o.data.avatarUrl : '',
|
|
25
|
+
href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const isRSSEnabled = config.rss?.enabled;
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<aside class="sticky top-28 left-0 h-full overflow-y-auto pr-6 py-4">
|
|
32
|
+
<div id="sidebar-cta-portal" class="">
|
|
33
|
+
{flow.data.versions && <VersionList versions={flow.data.versions} collectionItem={flow} />}
|
|
34
|
+
|
|
35
|
+
<OwnersList
|
|
36
|
+
title={`Flow owners (${ownersList.length})`}
|
|
37
|
+
owners={ownersList}
|
|
38
|
+
emptyMessage={`This flow does not have any documented owners.`}
|
|
39
|
+
client:load
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
isRSSEnabled && (
|
|
44
|
+
<div class="mx-auto pb-4 w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5 border-b border-gray-100 mb-4">
|
|
45
|
+
<span class="text-sm text-black group-data-[hover]:text-black/80 capitalize">Flow RSS Feed</span>
|
|
46
|
+
<ul role="list" class="space-y-2 mt-2">
|
|
47
|
+
<li class="has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal ">
|
|
48
|
+
<a class={`flex items-center space-x-2`} target="_blank" href={buildUrl(`/rss/flows/rss.xml`)}>
|
|
49
|
+
<RssIcon className="h-4 w-4 text-gray-800 group-hover:text-white" strokeWidth={1} />
|
|
50
|
+
<span class="font-light text-sm truncate">RSS</span>
|
|
51
|
+
</a>
|
|
52
|
+
</li>
|
|
53
|
+
</ul>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
<div class="space-y-2">
|
|
59
|
+
<a
|
|
60
|
+
href={buildUrl(`/visualiser/${flow.collection}/${flow.data.id}/${flow.data.version}`)}
|
|
61
|
+
class="flex items-center justify-center space-x-2 text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
|
|
62
|
+
>
|
|
63
|
+
<Workflow strokeWidth={2} size={16} />
|
|
64
|
+
<span class="block">View in visualiser</span>
|
|
65
|
+
</a>
|
|
66
|
+
<a
|
|
67
|
+
href={buildUrl(`/docs/${flow.collection}/${flow.data.id}/${flow.data.latestVersion}/changelog`)}
|
|
68
|
+
class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
|
|
69
|
+
>
|
|
70
|
+
<ScrollText strokeWidth={2} size={16} />
|
|
71
|
+
<span class="block">Read changelog</span>
|
|
72
|
+
</a>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</aside>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import CustomDocsNav from './';
|
|
2
|
+
import type { SidebarSection } from './types';
|
|
3
|
+
|
|
4
|
+
interface CustomDocsNavWrapperProps {
|
|
5
|
+
sidebarItems: SidebarSection[];
|
|
6
|
+
currentPath: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function CustomDocsNavWrapper(props: CustomDocsNavWrapperProps) {
|
|
10
|
+
return <CustomDocsNav {...props} />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import type { SidebarItem } from '../types';
|
|
4
|
+
|
|
5
|
+
interface NestedItemProps {
|
|
6
|
+
item: SidebarItem;
|
|
7
|
+
currentPath: string;
|
|
8
|
+
parentId: string;
|
|
9
|
+
itemIndex: number;
|
|
10
|
+
collapsedGroups: { [key: string]: boolean };
|
|
11
|
+
toggleGroupCollapse: (group: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NestedItem: React.FC<NestedItemProps> = ({
|
|
15
|
+
item,
|
|
16
|
+
currentPath,
|
|
17
|
+
parentId,
|
|
18
|
+
itemIndex,
|
|
19
|
+
collapsedGroups,
|
|
20
|
+
toggleGroupCollapse,
|
|
21
|
+
}) => {
|
|
22
|
+
const hasNestedItems = item.items && item.items.length > 0;
|
|
23
|
+
const itemId = `${parentId}-${itemIndex}`;
|
|
24
|
+
|
|
25
|
+
if (hasNestedItems && item.items) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="py-1">
|
|
28
|
+
<div className="flex items-center">
|
|
29
|
+
<button
|
|
30
|
+
className="p-1 hover:bg-gray-100 rounded-md"
|
|
31
|
+
onClick={(e) => {
|
|
32
|
+
e.stopPropagation();
|
|
33
|
+
toggleGroupCollapse(`nested-${itemId}`);
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<div className={`transition-transform duration-150 ${collapsedGroups[`nested-${itemId}`] ? '' : 'rotate-180'}`}>
|
|
37
|
+
<svg
|
|
38
|
+
className="h-3 w-3 text-gray-500"
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
width="16"
|
|
41
|
+
height="16"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
fill="none"
|
|
44
|
+
stroke="currentColor"
|
|
45
|
+
strokeWidth="2"
|
|
46
|
+
strokeLinecap="round"
|
|
47
|
+
strokeLinejoin="round"
|
|
48
|
+
>
|
|
49
|
+
<polyline points="6 9 12 15 18 9" />
|
|
50
|
+
</svg>
|
|
51
|
+
</div>
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
className="flex-grow flex items-center px-2 py-1 text-xs font-medium text-gray-700 rounded-md hover:bg-purple-50"
|
|
55
|
+
onClick={(e) => {
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
toggleGroupCollapse(`nested-${itemId}`);
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<span className="truncate">{item.label}</span>
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div
|
|
65
|
+
className={`overflow-hidden transition-[height] duration-150 ease-out ${
|
|
66
|
+
collapsedGroups[`nested-${itemId}`] ? 'h-0' : 'h-auto'
|
|
67
|
+
}`}
|
|
68
|
+
>
|
|
69
|
+
<div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-1">
|
|
70
|
+
{item.items.map((nestedItem: SidebarItem, nestedIndex: number) => {
|
|
71
|
+
if (nestedItem.items && nestedItem.items.length > 0) {
|
|
72
|
+
// Recursively render deeper nested items
|
|
73
|
+
const nestedItemId = `${itemId}-${nestedIndex}`;
|
|
74
|
+
return (
|
|
75
|
+
<div className="py-1" key={`nested-${nestedItemId}`}>
|
|
76
|
+
<div className="flex items-center">
|
|
77
|
+
<button
|
|
78
|
+
className="p-1 hover:bg-gray-100 rounded-md"
|
|
79
|
+
onClick={(e) => {
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
toggleGroupCollapse(`nested-${nestedItemId}`);
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
className={`transition-transform duration-150 ${collapsedGroups[`nested-${nestedItemId}`] ? '' : 'rotate-180'}`}
|
|
86
|
+
>
|
|
87
|
+
<svg
|
|
88
|
+
className="h-3 w-3 text-gray-500"
|
|
89
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
90
|
+
width="16"
|
|
91
|
+
height="16"
|
|
92
|
+
viewBox="0 0 24 24"
|
|
93
|
+
fill="none"
|
|
94
|
+
stroke="currentColor"
|
|
95
|
+
strokeWidth="2"
|
|
96
|
+
strokeLinecap="round"
|
|
97
|
+
strokeLinejoin="round"
|
|
98
|
+
>
|
|
99
|
+
<polyline points="6 9 12 15 18 9" />
|
|
100
|
+
</svg>
|
|
101
|
+
</div>
|
|
102
|
+
</button>
|
|
103
|
+
<button
|
|
104
|
+
className="flex-grow flex items-center px-2 py-1 text-xs font-medium text-gray-700 rounded-md hover:bg-purple-50"
|
|
105
|
+
onClick={(e) => {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
toggleGroupCollapse(`nested-${nestedItemId}`);
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<span className="truncate">{nestedItem.label}</span>
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div
|
|
115
|
+
className={`overflow-hidden transition-[height] duration-150 ease-out ${
|
|
116
|
+
collapsedGroups[`nested-${nestedItemId}`] ? 'h-0' : 'h-auto'
|
|
117
|
+
}`}
|
|
118
|
+
>
|
|
119
|
+
<div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-1">
|
|
120
|
+
{nestedItem.items.map((deepNestedItem: SidebarItem, deepIndex: number) => {
|
|
121
|
+
const deepNestedItemPath = deepNestedItem.slug ? buildUrl(`/docs/custom/${deepNestedItem.slug}`) : '#';
|
|
122
|
+
const isDeepActive =
|
|
123
|
+
currentPath === deepNestedItemPath || currentPath.endsWith(`/${deepNestedItem.slug}`);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<a
|
|
127
|
+
key={`deep-${nestedItemId}-${deepIndex}`}
|
|
128
|
+
href={deepNestedItemPath}
|
|
129
|
+
className={`flex items-center px-2 py-1.5 text-xs ${isDeepActive ? 'bg-purple-100 text-purple-900 font-medium' : 'text-gray-600 hover:bg-purple-100'} rounded-md`}
|
|
130
|
+
data-active={isDeepActive}
|
|
131
|
+
>
|
|
132
|
+
<span className="truncate">{deepNestedItem.label}</span>
|
|
133
|
+
</a>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const nestedItemPath = nestedItem.slug ? buildUrl(`/docs/custom/${nestedItem.slug}`) : '#';
|
|
143
|
+
const isActive = currentPath === nestedItemPath || currentPath.endsWith(`/${nestedItem.slug}`);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<a
|
|
147
|
+
key={`nested-link-${itemId}-${nestedIndex}`}
|
|
148
|
+
href={nestedItemPath}
|
|
149
|
+
className={`flex items-center px-2 py-1.5 text-xs ${isActive ? 'bg-purple-100 text-purple-900 font-medium' : 'text-gray-600 hover:bg-purple-100'} rounded-md`}
|
|
150
|
+
data-active={isActive}
|
|
151
|
+
>
|
|
152
|
+
<span className="truncate">{nestedItem.label}</span>
|
|
153
|
+
</a>
|
|
154
|
+
);
|
|
155
|
+
})}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const itemPath = item.slug ? buildUrl(`/docs/custom/${item.slug}`) : '#';
|
|
163
|
+
const isActive = currentPath === itemPath || currentPath.endsWith(`/${item.slug}`);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<a
|
|
167
|
+
href={itemPath}
|
|
168
|
+
className={`flex items-center px-2 py-1.5 text-xs ${isActive ? 'bg-purple-100 text-purple-900 font-medium' : 'text-gray-600 hover:bg-purple-100'} rounded-md`}
|
|
169
|
+
data-active={isActive}
|
|
170
|
+
>
|
|
171
|
+
<span className="truncate">{item.label}</span>
|
|
172
|
+
{item.badge && item?.badge?.text && (
|
|
173
|
+
<span
|
|
174
|
+
className={`text-${item.badge.color || 'purple'}-600 ml-2 text-[10px] font-medium bg-${item.badge.color || 'purple'}-50 px-2 py-0.5 rounded uppercase`}
|
|
175
|
+
>
|
|
176
|
+
{item.badge.text}
|
|
177
|
+
</span>
|
|
178
|
+
)}
|
|
179
|
+
</a>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default React.memo(NestedItem);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface NoResultsFoundProps {
|
|
4
|
+
searchTerm: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const NoResultsFound: React.FC<NoResultsFoundProps> = ({ searchTerm }) => (
|
|
8
|
+
<div className="px-4 py-6 text-center">
|
|
9
|
+
<div className="text-gray-400 text-sm mb-2">No results found for "{searchTerm}"</div>
|
|
10
|
+
<div className="text-gray-400 text-xs">
|
|
11
|
+
Try:
|
|
12
|
+
<ul className="mt-2 space-y-1 text-left list-disc pl-4">
|
|
13
|
+
<li>Checking for typos</li>
|
|
14
|
+
<li>Using fewer keywords</li>
|
|
15
|
+
<li>Using more general terms</li>
|
|
16
|
+
</ul>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export default React.memo(NoResultsFound);
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import type { CustomDocsNavProps, SidebarSection, SidebarItem } from './types';
|
|
4
|
+
import NestedItem from './components/NestedItem';
|
|
5
|
+
import NoResultsFound from './components/NoResultsFound';
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEY = 'EventCatalog:customDocsSidebarCollapsedGroups';
|
|
8
|
+
const DEBOUNCE_DELAY = 300; // 300ms debounce delay
|
|
9
|
+
|
|
10
|
+
const CustomDocsNav: React.FC<CustomDocsNavProps> = ({ sidebarItems, currentPath }) => {
|
|
11
|
+
const navRef = useRef<HTMLElement>(null);
|
|
12
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
13
|
+
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
|
14
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
15
|
+
const [collapsedGroups, setCollapsedGroups] = useState<{ [key: string]: boolean }>(() => {
|
|
16
|
+
if (typeof window !== 'undefined') {
|
|
17
|
+
const saved = window.localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
setIsInitialized(true);
|
|
19
|
+
return saved ? JSON.parse(saved) : {};
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Set up debounced search
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
setDebouncedSearchTerm(searchTerm.toLowerCase());
|
|
28
|
+
}, DEBOUNCE_DELAY);
|
|
29
|
+
|
|
30
|
+
return () => clearTimeout(timer);
|
|
31
|
+
}, [searchTerm]);
|
|
32
|
+
|
|
33
|
+
// Filter sidebar items based on search term
|
|
34
|
+
const filteredSidebarItems = useMemo(() => {
|
|
35
|
+
if (!debouncedSearchTerm) return sidebarItems;
|
|
36
|
+
|
|
37
|
+
const matchesSearchTerm = (text: string) => text.toLowerCase().includes(debouncedSearchTerm);
|
|
38
|
+
|
|
39
|
+
// Helper function to check if an item or any of its nested items match the search term
|
|
40
|
+
const itemContainsSearchTerm = (item: SidebarItem): boolean => {
|
|
41
|
+
if (matchesSearchTerm(item.label)) return true;
|
|
42
|
+
|
|
43
|
+
if (item.items && item.items.length > 0) {
|
|
44
|
+
return item.items.some(itemContainsSearchTerm);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return sidebarItems
|
|
51
|
+
.map((section) => {
|
|
52
|
+
if (!section.items) {
|
|
53
|
+
return matchesSearchTerm(section.label) ? section : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const filteredItems = section.items.filter(itemContainsSearchTerm);
|
|
57
|
+
|
|
58
|
+
if (filteredItems.length > 0 || matchesSearchTerm(section.label)) {
|
|
59
|
+
return {
|
|
60
|
+
...section,
|
|
61
|
+
items: filteredItems,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
})
|
|
67
|
+
.filter(Boolean) as SidebarSection[];
|
|
68
|
+
}, [sidebarItems, debouncedSearchTerm]);
|
|
69
|
+
|
|
70
|
+
// Auto-expand groups when searching
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (debouncedSearchTerm) {
|
|
73
|
+
// Expand all groups when searching
|
|
74
|
+
const newCollapsedState = { ...collapsedGroups };
|
|
75
|
+
Object.keys(newCollapsedState).forEach((key) => {
|
|
76
|
+
newCollapsedState[key] = false;
|
|
77
|
+
});
|
|
78
|
+
setCollapsedGroups(newCollapsedState);
|
|
79
|
+
}
|
|
80
|
+
}, [debouncedSearchTerm]);
|
|
81
|
+
|
|
82
|
+
// Store collapsed groups in local storage
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (typeof window !== 'undefined' && isInitialized) {
|
|
85
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedGroups));
|
|
86
|
+
}
|
|
87
|
+
}, [collapsedGroups, isInitialized]);
|
|
88
|
+
|
|
89
|
+
// Initialize collapsed state from section config
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (isInitialized && sidebarItems && sidebarItems.length > 0) {
|
|
92
|
+
const initialState = { ...collapsedGroups };
|
|
93
|
+
|
|
94
|
+
sidebarItems.forEach((section, index) => {
|
|
95
|
+
const sectionKey = `section-${index}`;
|
|
96
|
+
if (section.collapsed !== undefined && initialState[sectionKey] === undefined) {
|
|
97
|
+
initialState[sectionKey] = section.collapsed;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
setCollapsedGroups(initialState);
|
|
102
|
+
}
|
|
103
|
+
}, [sidebarItems, isInitialized]);
|
|
104
|
+
|
|
105
|
+
// If we find a data-active element, scroll to it on mount
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const activeElement = document.querySelector('[data-active="true"]');
|
|
108
|
+
if (activeElement) {
|
|
109
|
+
// Add y offset to the scroll position
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
112
|
+
}, 300);
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const toggleGroupCollapse = useCallback((group: string) => {
|
|
117
|
+
setCollapsedGroups((prev) => ({
|
|
118
|
+
...prev,
|
|
119
|
+
[group]: !prev[group],
|
|
120
|
+
}));
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
124
|
+
setSearchTerm(e.target.value);
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
if (!isInitialized) return null;
|
|
128
|
+
|
|
129
|
+
const hasNoResults = debouncedSearchTerm && filteredSidebarItems.length === 0;
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<nav ref={navRef} className="h-full text-gray-800 pt-2">
|
|
133
|
+
<div className="mb-2 px-4">
|
|
134
|
+
<input
|
|
135
|
+
type="text"
|
|
136
|
+
value={searchTerm}
|
|
137
|
+
onChange={handleSearchChange}
|
|
138
|
+
placeholder="Quick search..."
|
|
139
|
+
className="w-full p-2 text-sm rounded-md border border-gray-200 h-[30px]"
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="space-y-2 divide-y divide-gray-100/40">
|
|
144
|
+
{hasNoResults ? (
|
|
145
|
+
<NoResultsFound searchTerm={debouncedSearchTerm} />
|
|
146
|
+
) : (
|
|
147
|
+
filteredSidebarItems.map((section: SidebarSection, index: number) => (
|
|
148
|
+
<div className="pt-2 pb-2 px-4" key={`section-${index}`}>
|
|
149
|
+
<div className="space-y-0" data-section={`section-${index}`}>
|
|
150
|
+
{section.items ? (
|
|
151
|
+
<div className="flex items-center">
|
|
152
|
+
<button
|
|
153
|
+
className="p-1 hover:bg-gray-100 rounded-md"
|
|
154
|
+
onClick={(e) => {
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
toggleGroupCollapse(`section-${index}`);
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<div
|
|
160
|
+
className={`transition-transform duration-150 ${collapsedGroups[`section-${index}`] ? '' : 'rotate-180'}`}
|
|
161
|
+
>
|
|
162
|
+
<svg
|
|
163
|
+
className="h-3 w-3 text-gray-500"
|
|
164
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
165
|
+
width="16"
|
|
166
|
+
height="16"
|
|
167
|
+
viewBox="0 0 24 24"
|
|
168
|
+
fill="none"
|
|
169
|
+
stroke="currentColor"
|
|
170
|
+
strokeWidth="2"
|
|
171
|
+
strokeLinecap="round"
|
|
172
|
+
strokeLinejoin="round"
|
|
173
|
+
>
|
|
174
|
+
<polyline points="6 9 12 15 18 9" />
|
|
175
|
+
</svg>
|
|
176
|
+
</div>
|
|
177
|
+
</button>
|
|
178
|
+
<button
|
|
179
|
+
className="flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md hover:bg-purple-50"
|
|
180
|
+
onClick={(e) => {
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
toggleGroupCollapse(`section-${index}`);
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<span className="truncate">{section.label}</span>
|
|
186
|
+
{section.badge && section?.badge?.text && (
|
|
187
|
+
<span
|
|
188
|
+
className={`text-${section.badge.color || 'purple'}-600 ml-2 text-[10px] font-medium bg-${section.badge.color || 'purple'}-50 px-2 py-0.5 rounded uppercase`}
|
|
189
|
+
>
|
|
190
|
+
{section.badge.text}
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
) : (
|
|
196
|
+
<div className="flex items-center">
|
|
197
|
+
<span className="flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md">
|
|
198
|
+
<span className="truncate">{section.label}</span>
|
|
199
|
+
<span className="text-purple-600 ml-2 text-[10px] font-medium bg-purple-50 px-2 py-0.5 rounded uppercase">
|
|
200
|
+
Section
|
|
201
|
+
</span>
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{section.items && (
|
|
207
|
+
<div
|
|
208
|
+
className={`overflow-hidden transition-[height] duration-150 ease-out ${
|
|
209
|
+
collapsedGroups[`section-${index}`] ? 'h-0' : 'h-auto'
|
|
210
|
+
}`}
|
|
211
|
+
>
|
|
212
|
+
<div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-1">
|
|
213
|
+
{section.items.map((item: SidebarItem, itemIndex: number) => (
|
|
214
|
+
<NestedItem
|
|
215
|
+
key={`item-${index}-${itemIndex}`}
|
|
216
|
+
item={item}
|
|
217
|
+
currentPath={currentPath}
|
|
218
|
+
parentId={`${index}`}
|
|
219
|
+
itemIndex={itemIndex}
|
|
220
|
+
collapsedGroups={collapsedGroups}
|
|
221
|
+
toggleGroupCollapse={toggleGroupCollapse}
|
|
222
|
+
/>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{section.slug && !section.items && (
|
|
229
|
+
<a
|
|
230
|
+
href={buildUrl(`/docs/custom/${section.slug}`)}
|
|
231
|
+
className={`flex items-center px-2 py-1.5 text-xs ${
|
|
232
|
+
currentPath.endsWith(`/${section.slug}`)
|
|
233
|
+
? 'bg-purple-100 text-purple-900 font-medium'
|
|
234
|
+
: 'text-gray-600 hover:bg-purple-100'
|
|
235
|
+
} rounded-md ml-6`}
|
|
236
|
+
data-active={currentPath.endsWith(`/${section.slug}`)}
|
|
237
|
+
>
|
|
238
|
+
<span className="truncate">{section.label}</span>
|
|
239
|
+
</a>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
))
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
</nav>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export default React.memo(CustomDocsNav);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SidebarItem {
|
|
2
|
+
label: string;
|
|
3
|
+
slug?: string;
|
|
4
|
+
items?: SidebarItem[];
|
|
5
|
+
badge?: {
|
|
6
|
+
text: string;
|
|
7
|
+
color: string;
|
|
8
|
+
};
|
|
9
|
+
collapsed?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SidebarSection {
|
|
13
|
+
label: string;
|
|
14
|
+
items?: SidebarItem[];
|
|
15
|
+
slug?: string;
|
|
16
|
+
autogenerated?: {
|
|
17
|
+
directory: string;
|
|
18
|
+
};
|
|
19
|
+
badge?: {
|
|
20
|
+
text: string;
|
|
21
|
+
color: string;
|
|
22
|
+
};
|
|
23
|
+
collapsed?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CustomDocsNavProps {
|
|
27
|
+
sidebarItems: SidebarSection[];
|
|
28
|
+
currentPath: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getNavigationItems } from '@enterprise/custom-documentation/utils/custom-docs';
|
|
3
|
+
import CustomDocsNavWrapper from '@enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper';
|
|
4
|
+
|
|
5
|
+
const currentPath = Astro.url.pathname;
|
|
6
|
+
const sidebarItems = await getNavigationItems();
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<CustomDocsNavWrapper client:load sidebarItems={sidebarItems} currentPath={currentPath} />
|
|
@@ -115,8 +115,8 @@ function groupChildrenByType(parentNode: TreeNode) {
|
|
|
115
115
|
acc[n.type].push(n);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
// Collapse
|
|
119
|
-
const AUTO_EXPANDED_TYPES = ['domains'
|
|
118
|
+
// Collapse everything except domains
|
|
119
|
+
const AUTO_EXPANDED_TYPES = ['domains'];
|
|
120
120
|
|
|
121
121
|
parentNode.children = Object.entries(acc)
|
|
122
122
|
// Order label nodes by RESOURCE_TYPES
|