@eventcatalog/core 4.9.0 → 4.10.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/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-XQHS36GO.js → chunk-5XURY2JV.js} +1 -1
- package/dist/{chunk-3CGSSMTK.js → chunk-BDFTJVGN.js} +1 -1
- package/dist/{chunk-HKVOSQTD.js → chunk-BQXEWQ5J.js} +1 -1
- package/dist/{chunk-MPHX5C7M.js → chunk-HFAQXAG3.js} +1 -1
- package/dist/{chunk-LVGQ23LR.js → chunk-IC6XSXJO.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/Badge.astro +3 -1
- package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +0 -5
- package/eventcatalog/src/components/MDX/Design/Design.astro +2 -0
- package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -0
- package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +5 -7
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +121 -88
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +55 -7
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +59 -15
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +53 -7
- package/eventcatalog/src/content.config.ts +70 -0
- package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +19 -0
- package/eventcatalog/src/enterprise/fields/pages/fields.astro +2 -0
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +16 -15
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +21 -17
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +16 -8
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +48 -65
- package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +45 -18
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -1
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +5 -1
- package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +2 -0
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +96 -34
- package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +167 -75
- package/eventcatalog/src/stores/sidebar-store/builders/container.ts +135 -59
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +144 -58
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +306 -188
- package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +108 -39
- package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +147 -63
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +212 -121
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +223 -127
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +3 -23
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +187 -97
- package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +678 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +66 -36
- package/eventcatalog/src/styles/tailwind.css +82 -0
- package/eventcatalog/src/utils/collections/resource-docs.ts +1 -0
- package/package.json +3 -3
|
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
140
140
|
var import_os = __toESM(require("os"), 1);
|
|
141
141
|
|
|
142
142
|
// package.json
|
|
143
|
-
var version = "4.
|
|
143
|
+
var version = "4.10.0";
|
|
144
144
|
|
|
145
145
|
// src/constants.ts
|
|
146
146
|
var VERSION = version;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-HFAQXAG3.js";
|
|
4
|
+
import "../chunk-BQXEWQ5J.js";
|
|
5
5
|
import "../chunk-K2XIENVT.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-BDFTJVGN.js";
|
|
7
7
|
import "../chunk-6QENHZZP.js";
|
|
8
8
|
export {
|
|
9
9
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
|
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
144
144
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
145
145
|
|
|
146
146
|
// package.json
|
|
147
|
-
var version = "4.
|
|
147
|
+
var version = "4.10.0";
|
|
148
148
|
|
|
149
149
|
// src/constants.ts
|
|
150
150
|
var VERSION = version;
|
package/dist/eventcatalog.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
import "./chunk-CA4U2JP7.js";
|
|
5
5
|
import {
|
|
6
6
|
log_build_default
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-HFAQXAG3.js";
|
|
8
|
+
import "./chunk-BQXEWQ5J.js";
|
|
9
9
|
import "./chunk-K2XIENVT.js";
|
|
10
10
|
import {
|
|
11
11
|
FederationConflictError,
|
|
@@ -31,10 +31,10 @@ import {
|
|
|
31
31
|
} from "./chunk-B7HCX5HM.js";
|
|
32
32
|
import {
|
|
33
33
|
generate
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-5XURY2JV.js";
|
|
35
35
|
import {
|
|
36
36
|
logger
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-IC6XSXJO.js";
|
|
38
38
|
import {
|
|
39
39
|
resolve_catalog_dependencies_default
|
|
40
40
|
} from "./chunk-LHR4G2UO.js";
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
import "./chunk-W5JQON7Z.js";
|
|
55
55
|
import {
|
|
56
56
|
VERSION
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-BDFTJVGN.js";
|
|
58
58
|
import {
|
|
59
59
|
linkCoreNodeModules,
|
|
60
60
|
resolveInstalledCoreNodeModules
|
package/dist/generate.cjs
CHANGED
|
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
|
|
|
108
108
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
109
109
|
|
|
110
110
|
// package.json
|
|
111
|
-
var version = "4.
|
|
111
|
+
var version = "4.10.0";
|
|
112
112
|
|
|
113
113
|
// src/constants.ts
|
|
114
114
|
var VERSION = version;
|
package/dist/generate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generate
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-5XURY2JV.js";
|
|
4
|
+
import "./chunk-IC6XSXJO.js";
|
|
5
|
+
import "./chunk-BDFTJVGN.js";
|
|
6
6
|
import "./chunk-6QENHZZP.js";
|
|
7
7
|
export {
|
|
8
8
|
generate
|
package/dist/utils/cli-logger.js
CHANGED
|
@@ -23,6 +23,8 @@ type Props = {
|
|
|
23
23
|
|
|
24
24
|
const { badge, className = '' } = Astro.props;
|
|
25
25
|
const href = getBadgeHref(badge);
|
|
26
|
+
// Only links that leave the catalog get the external-link arrow.
|
|
27
|
+
const isExternalHref = !!href && (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith('//'));
|
|
26
28
|
const Icon = badge.icon || badge.iconComponent;
|
|
27
29
|
const hasThemedIcon = badge.iconURLLight && badge.iconURLDark;
|
|
28
30
|
const classes = `
|
|
@@ -59,7 +61,7 @@ const classes = `
|
|
|
59
61
|
<img src={badge.iconURL} class="w-3 h-3 flex-shrink-0 opacity-80" alt="" onerror="this.style.display='none'" />
|
|
60
62
|
)}
|
|
61
63
|
<span>{badge.content}</span>
|
|
62
|
-
<ArrowTopRightOnSquareIcon className="w-2.5 h-2.5 flex-shrink-0 opacity-70" aria-hidden="true" />
|
|
64
|
+
{isExternalHref && <ArrowTopRightOnSquareIcon className="w-2.5 h-2.5 flex-shrink-0 opacity-70" aria-hidden="true" />}
|
|
63
65
|
</a>
|
|
64
66
|
) : (
|
|
65
67
|
<span id={badge.id || undefined} class={classes} style={getBadgeStyle(badge)} title={badge.content}>
|
|
@@ -224,11 +224,6 @@ function FieldNodeGraphInner({
|
|
|
224
224
|
const { fitView } = useReactFlow();
|
|
225
225
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
226
226
|
|
|
227
|
-
// Load visualiser styles (animations, hover effects, theme variables)
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
import('@eventcatalog/visualiser/styles-core.css');
|
|
230
|
-
}, []);
|
|
231
|
-
|
|
232
227
|
useEffect(() => {
|
|
233
228
|
const timer = setTimeout(() => fitView({ padding: 0.2 }), 50);
|
|
234
229
|
return () => clearTimeout(timer);
|
|
@@ -4,6 +4,8 @@ import fs from 'fs';
|
|
|
4
4
|
import { getAbsoluteFilePathForAstroFile } from '@utils/files';
|
|
5
5
|
import Admonition from '@components/MDX/Admonition';
|
|
6
6
|
import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
|
|
7
|
+
// Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
|
|
8
|
+
import '@eventcatalog/visualiser/styles-core.css';
|
|
7
9
|
|
|
8
10
|
import { isVisualiserEnabled, isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
|
|
9
11
|
import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
|
|
@@ -3,6 +3,8 @@ import { getDomains } from '@utils/collections/domains';
|
|
|
3
3
|
import { getNodesAndEdges } from '@utils/node-graphs/domain-entity-map.ts';
|
|
4
4
|
import Admonition from '@components/MDX/Admonition';
|
|
5
5
|
import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
|
|
6
|
+
// Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
|
|
7
|
+
import '@eventcatalog/visualiser/styles-core.css';
|
|
6
8
|
import { getVersionFromCollection } from '@utils/collections/versions';
|
|
7
9
|
import { getServices } from '@utils/collections/services';
|
|
8
10
|
import { isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
|
|
@@ -3,6 +3,8 @@ import { getFlows } from '@utils/collections/flows';
|
|
|
3
3
|
import { getNodesAndEdges } from '@utils/node-graphs/flows-node-graph';
|
|
4
4
|
import Admonition from '@components/MDX/Admonition';
|
|
5
5
|
import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
|
|
6
|
+
// Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
|
|
7
|
+
import '@eventcatalog/visualiser/styles-core.css';
|
|
6
8
|
import { getVersionFromCollection } from '@utils/collections/versions';
|
|
7
9
|
import { isVisualiserEnabled, isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
|
|
8
10
|
import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
* - Navigation using Astro's navigate function
|
|
7
7
|
* - Layout persistence via Astro API routes
|
|
8
8
|
* - URL building with Astro's URL utilities
|
|
9
|
+
*
|
|
10
|
+
* Note: the visualiser stylesheet is deliberately NOT imported here. Every Astro file that
|
|
11
|
+
* renders this component imports `@eventcatalog/visualiser/styles-core.css` itself so the
|
|
12
|
+
* CSS ships in the page head and survives ClientRouter navigations (see NodeGraph.astro).
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
|
-
import { useCallback, lazy, Suspense
|
|
15
|
+
import { useCallback, lazy, Suspense } from 'react';
|
|
12
16
|
import type { Node, Edge } from '@xyflow/react';
|
|
13
17
|
import { buildUrl } from '@utils/url-builder';
|
|
14
18
|
|
|
@@ -42,12 +46,6 @@ interface AstroNodeGraphProps {
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
const AstroNodeGraph = ({ isDevMode = false, resourceKey, ...otherProps }: AstroNodeGraphProps) => {
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
// Load visualizer styles only when this component is mounted.
|
|
47
|
-
// Use the core-scoped stylesheet so visualizer styles don't override EventCatalog utility classes globally.
|
|
48
|
-
import('@eventcatalog/visualiser/styles-core.css');
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
49
|
// Astro-specific navigation handler
|
|
52
50
|
const handleNavigate = useCallback((url: string) => {
|
|
53
51
|
// Use window.location for navigation since we can't import astro:transitions/client in a React component
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import AstroNodeGraph from './AstroNodeGraph';
|
|
3
|
+
// Imported here (server side) rather than from the React island: Astro then emits it as a
|
|
4
|
+
// <link> in the page head, which survives ClientRouter navigations. A dynamic import from the
|
|
5
|
+
// island injects a <style> that the view-transition head swap removes — and the cached module
|
|
6
|
+
// never re-injects it, leaving the graph unstyled until a full reload.
|
|
7
|
+
import '@eventcatalog/visualiser/styles-core.css';
|
|
3
8
|
import { getNodesAndEdges as getNodesAndEdgesForAgent } from '@utils/node-graphs/agents-node-graph';
|
|
4
9
|
import { getNodesAndEdges as getNodesAndEdgesForService } from '@utils/node-graphs/services-node-graph';
|
|
5
10
|
import {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
3
|
+
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import * as LucideIcons from 'lucide-react';
|
|
5
5
|
import { ChevronRight, ChevronLeft, ChevronDown, Home, Star } from 'lucide-react';
|
|
6
6
|
import type { NavNode, ChildRef } from '@stores/sidebar-store/state';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
saveState,
|
|
9
|
+
loadState,
|
|
10
|
+
saveCollapsedSections,
|
|
11
|
+
loadCollapsedSections,
|
|
12
|
+
saveScrollPosition,
|
|
13
|
+
loadScrollPosition,
|
|
14
|
+
} from './storage';
|
|
8
15
|
import { useStore } from '@nanostores/react';
|
|
9
16
|
import { sidebarStore } from '@stores/sidebar-store';
|
|
10
17
|
import {
|
|
@@ -13,7 +20,14 @@ import {
|
|
|
13
20
|
removeFavorite as removeFavoriteAction,
|
|
14
21
|
type FavoriteItem,
|
|
15
22
|
} from '@stores/favorites-store';
|
|
16
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
canCollapseGroup,
|
|
25
|
+
findNodeKeyByUrl,
|
|
26
|
+
getBadgeClasses,
|
|
27
|
+
getDefaultCollapsedState,
|
|
28
|
+
isGroupCollapsed,
|
|
29
|
+
toggleGroupCollapsed,
|
|
30
|
+
} from './utils';
|
|
17
31
|
import { resolveIconUrl } from '@utils/icon';
|
|
18
32
|
|
|
19
33
|
const cn = (...classes: (string | false | undefined)[]) => classes.filter(Boolean).join(' ');
|
|
@@ -60,6 +74,7 @@ export default function NestedSideBar() {
|
|
|
60
74
|
const [sectionCollapsePreferences, setSectionCollapsePreferences] = useState(loadCollapsedSections);
|
|
61
75
|
const [showPathPreview, setShowPathPreview] = useState(false);
|
|
62
76
|
const [showFullPath, setShowFullPath] = useState(false);
|
|
77
|
+
const navRef = useRef<HTMLElement | null>(null);
|
|
63
78
|
|
|
64
79
|
// Build a lookup map for faster URL navigation
|
|
65
80
|
// Map format: "type:id" -> "nodeKey"
|
|
@@ -94,19 +109,9 @@ export default function NestedSideBar() {
|
|
|
94
109
|
/**
|
|
95
110
|
* Toggle section collapse state
|
|
96
111
|
*/
|
|
97
|
-
const toggleSectionCollapse = (sectionId: string) => {
|
|
112
|
+
const toggleSectionCollapse = (sectionId: string, defaultCollapsed = true) => {
|
|
98
113
|
setSectionCollapsePreferences((previousPreferences) => {
|
|
99
|
-
const nextPreferences =
|
|
100
|
-
expanded: new Set(previousPreferences.expanded),
|
|
101
|
-
};
|
|
102
|
-
const isCurrentlyCollapsed = isGroupCollapsed(true, sectionId, previousPreferences);
|
|
103
|
-
|
|
104
|
-
if (isCurrentlyCollapsed) {
|
|
105
|
-
nextPreferences.expanded.add(sectionId);
|
|
106
|
-
} else {
|
|
107
|
-
nextPreferences.expanded.delete(sectionId);
|
|
108
|
-
}
|
|
109
|
-
|
|
114
|
+
const nextPreferences = toggleGroupCollapsed(sectionId, previousPreferences, defaultCollapsed);
|
|
110
115
|
saveCollapsedSections(nextPreferences);
|
|
111
116
|
return nextPreferences;
|
|
112
117
|
});
|
|
@@ -416,6 +421,50 @@ export default function NestedSideBar() {
|
|
|
416
421
|
};
|
|
417
422
|
}, [isInitialized, findAndNavigateToUrl, currentPath]);
|
|
418
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Keep the selected item visible. On a full page load the nav remounts scrolled to the
|
|
426
|
+
* top, so first restore the offset saved for this drill-down path, then nudge the
|
|
427
|
+
* active item into view if it still isn't. `block: 'nearest'` never scrolls when the
|
|
428
|
+
* item is already visible, so clicking around near the top doesn't jump.
|
|
429
|
+
*/
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
if (!isInitialized) return;
|
|
432
|
+
const nav = navRef.current;
|
|
433
|
+
if (!nav) return;
|
|
434
|
+
|
|
435
|
+
const pathKey = getCurrentPath().join('/') || 'root';
|
|
436
|
+
const savedScrollTop = loadScrollPosition(pathKey);
|
|
437
|
+
if (savedScrollTop !== null) nav.scrollTop = savedScrollTop;
|
|
438
|
+
|
|
439
|
+
const active = nav.querySelector<HTMLElement>('[data-active="true"]');
|
|
440
|
+
if (!active) return;
|
|
441
|
+
|
|
442
|
+
const navRect = nav.getBoundingClientRect();
|
|
443
|
+
const activeRect = active.getBoundingClientRect();
|
|
444
|
+
const isVisible = activeRect.top >= navRect.top && activeRect.bottom <= navRect.bottom;
|
|
445
|
+
if (!isVisible) active.scrollIntoView({ block: 'nearest' });
|
|
446
|
+
}, [isInitialized, currentPath, navigationStack, getCurrentPath]);
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Persist the nav scroll offset as it changes.
|
|
450
|
+
*/
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (!isInitialized) return;
|
|
453
|
+
const nav = navRef.current;
|
|
454
|
+
if (!nav) return;
|
|
455
|
+
|
|
456
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
457
|
+
const onScroll = () => {
|
|
458
|
+
clearTimeout(timer);
|
|
459
|
+
timer = setTimeout(() => saveScrollPosition(getCurrentPath().join('/') || 'root', nav.scrollTop), 150);
|
|
460
|
+
};
|
|
461
|
+
nav.addEventListener('scroll', onScroll, { passive: true });
|
|
462
|
+
return () => {
|
|
463
|
+
clearTimeout(timer);
|
|
464
|
+
nav.removeEventListener('scroll', onScroll);
|
|
465
|
+
};
|
|
466
|
+
}, [isInitialized, navigationStack, getCurrentPath]);
|
|
467
|
+
|
|
419
468
|
/**
|
|
420
469
|
* Check if a node is favorited
|
|
421
470
|
* Note: This hook must be defined before any early returns to comply with Rules of Hooks
|
|
@@ -658,14 +707,15 @@ export default function NestedSideBar() {
|
|
|
658
707
|
};
|
|
659
708
|
|
|
660
709
|
/**
|
|
661
|
-
* Render a group with its children
|
|
710
|
+
* Render a group with its children.
|
|
711
|
+
*
|
|
712
|
+
* Groups render as a docs-style tree: every group has its caret on the right; top-level
|
|
713
|
+
* sections also carry an icon tile. Each level's children hang off a vertical guide.
|
|
662
714
|
*/
|
|
663
|
-
const renderGroup = (group: NavNode, groupKey: string | null, index: number) => {
|
|
664
|
-
// Get optional icon for group
|
|
715
|
+
const renderGroup = (group: NavNode, groupKey: string | null, index: number, depth = 0) => {
|
|
665
716
|
const GroupIcon = group.icon ? (LucideIcons as unknown as Record<string, LucideIcons.LucideIcon>)[group.icon] : null;
|
|
666
|
-
const
|
|
717
|
+
const isNested = depth > 0;
|
|
667
718
|
|
|
668
|
-
// Get visible children
|
|
669
719
|
const visibleChildren =
|
|
670
720
|
group.pages?.filter((childRef) => {
|
|
671
721
|
const child = resolveRef(childRef);
|
|
@@ -673,46 +723,36 @@ export default function NestedSideBar() {
|
|
|
673
723
|
}) ?? [];
|
|
674
724
|
|
|
675
725
|
const groupId = groupKey || group.collapseKey || `${currentLevel.key ?? 'root'}:group:${group.title}`;
|
|
676
|
-
const canCollapse = canCollapseGroup(
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
// they render flush under the parent icon instead of inside the indented border guide.
|
|
681
|
-
const hasSubtleChildren = visibleChildren.some((childRef) => {
|
|
682
|
-
const child = resolveRef(childRef);
|
|
683
|
-
return child && isGroup(child) && child.subtle === true;
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
const headerContent = (
|
|
726
|
+
const canCollapse = canCollapseGroup(isTopLevel, group.collapsible, group.collapsed);
|
|
727
|
+
const defaultCollapsed = getDefaultCollapsedState(visibleChildren.length, group.collapsed);
|
|
728
|
+
const isCollapsed = isGroupCollapsed(canCollapse, groupId, sectionCollapsePreferences, defaultCollapsed);
|
|
729
|
+
const header = isNested ? (
|
|
687
730
|
<>
|
|
688
|
-
<
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
'flex items-center justify-center w-5 h-5 rounded',
|
|
693
|
-
isSubtleGroup
|
|
694
|
-
? 'bg-[rgb(var(--ec-content-hover))] text-[rgb(var(--ec-content-text-muted))]'
|
|
695
|
-
: 'bg-[rgb(var(--ec-group-icon-bg))] text-[rgb(var(--ec-group-icon-text))]'
|
|
696
|
-
)}
|
|
697
|
-
>
|
|
698
|
-
<GroupIcon className="w-3 h-3" />
|
|
699
|
-
</span>
|
|
700
|
-
)}
|
|
701
|
-
<span
|
|
731
|
+
{GroupIcon && <GroupIcon className="w-3.5 h-3.5 flex-shrink-0 text-[rgb(var(--ec-content-text-muted))]" />}
|
|
732
|
+
<span className="text-[12px] font-medium text-[rgb(var(--ec-content-text))] truncate">{group.title}</span>
|
|
733
|
+
{canCollapse && (
|
|
734
|
+
<ChevronDown
|
|
702
735
|
className={cn(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
: 'text-[12px] font-semibold tracking-tight text-[rgb(var(--ec-content-text))]'
|
|
736
|
+
'ml-auto w-3.5 h-3.5 flex-shrink-0 text-[rgb(var(--ec-icon-color))] transition-transform',
|
|
737
|
+
isCollapsed && '-rotate-90'
|
|
706
738
|
)}
|
|
707
|
-
|
|
708
|
-
|
|
739
|
+
/>
|
|
740
|
+
)}
|
|
741
|
+
</>
|
|
742
|
+
) : (
|
|
743
|
+
<>
|
|
744
|
+
{GroupIcon && (
|
|
745
|
+
<span className="flex items-center justify-center w-5 h-5 flex-shrink-0 rounded bg-[rgb(var(--ec-group-icon-bg))] text-[rgb(var(--ec-group-icon-text))]">
|
|
746
|
+
<GroupIcon className="w-3 h-3" />
|
|
709
747
|
</span>
|
|
710
|
-
|
|
748
|
+
)}
|
|
749
|
+
<span className="text-[12px] font-semibold tracking-tight text-[rgb(var(--ec-content-text))] truncate">
|
|
750
|
+
{group.title}
|
|
751
|
+
</span>
|
|
711
752
|
{canCollapse && (
|
|
712
753
|
<ChevronDown
|
|
713
754
|
className={cn(
|
|
714
|
-
|
|
715
|
-
'text-[rgb(var(--ec-icon-color))] transition-transform',
|
|
755
|
+
'ml-auto w-4 h-4 flex-shrink-0 text-[rgb(var(--ec-icon-color))] transition-transform',
|
|
716
756
|
isCollapsed && '-rotate-90'
|
|
717
757
|
)}
|
|
718
758
|
/>
|
|
@@ -720,32 +760,30 @@ export default function NestedSideBar() {
|
|
|
720
760
|
</>
|
|
721
761
|
);
|
|
722
762
|
|
|
763
|
+
const headerClasses = cn(
|
|
764
|
+
'group flex items-center w-full rounded-md text-left transition-colors',
|
|
765
|
+
isNested ? 'gap-1.5 px-2 py-1.5' : 'gap-2 px-2 py-1.5',
|
|
766
|
+
canCollapse && 'cursor-pointer hover:bg-[rgb(var(--ec-content-hover))]'
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// Top level: the guide line sits under the centre of the icon tile (8px padding + half of
|
|
770
|
+
// 20px). Nested: carets sit on the right, so the guide is simply inset from the label.
|
|
771
|
+
const childrenClasses = cn(
|
|
772
|
+
'flex flex-col gap-px mt-0.5 border-l border-[rgb(var(--ec-content-border))]',
|
|
773
|
+
isNested ? 'ml-3 pl-1.5' : 'ml-[18px] pl-2'
|
|
774
|
+
);
|
|
775
|
+
|
|
723
776
|
return (
|
|
724
|
-
<div key={`group-${groupKey || index}`} className={cn(
|
|
777
|
+
<div key={`group-${groupKey || index}`} className={cn(isNested ? 'mt-0.5' : 'mb-4 last:mb-1')}>
|
|
725
778
|
{canCollapse ? (
|
|
726
|
-
<button
|
|
727
|
-
|
|
728
|
-
className={cn(
|
|
729
|
-
'flex items-center justify-between w-full rounded-md transition-colors cursor-pointer',
|
|
730
|
-
isSubtleGroup
|
|
731
|
-
? 'px-2 py-1 hover:bg-[rgb(var(--ec-content-hover))]/60'
|
|
732
|
-
: 'px-2 py-1.5 hover:bg-[rgb(var(--ec-content-hover))]'
|
|
733
|
-
)}
|
|
734
|
-
>
|
|
735
|
-
{headerContent}
|
|
779
|
+
<button type="button" onClick={() => toggleSectionCollapse(groupId, defaultCollapsed)} className={headerClasses}>
|
|
780
|
+
{header}
|
|
736
781
|
</button>
|
|
737
782
|
) : (
|
|
738
|
-
<div className={
|
|
739
|
-
{headerContent}
|
|
740
|
-
</div>
|
|
783
|
+
<div className={headerClasses}>{header}</div>
|
|
741
784
|
)}
|
|
742
785
|
{!isCollapsed && (
|
|
743
|
-
<div
|
|
744
|
-
className={cn(
|
|
745
|
-
'flex flex-col gap-0.5 border-[rgb(var(--ec-content-border))]',
|
|
746
|
-
isSubtleGroup ? 'border-l ml-4 mt-1' : hasSubtleChildren ? 'mt-1' : 'border-l ml-4 mt-1'
|
|
747
|
-
)}
|
|
748
|
-
>
|
|
786
|
+
<div className={childrenClasses}>
|
|
749
787
|
{visibleChildren.map((childRef, childIndex) => {
|
|
750
788
|
const child = resolveRef(childRef);
|
|
751
789
|
if (!child) return null;
|
|
@@ -753,22 +791,13 @@ export default function NestedSideBar() {
|
|
|
753
791
|
const childKey = typeof childRef === 'string' ? childRef : null;
|
|
754
792
|
|
|
755
793
|
if (isGroup(child)) {
|
|
756
|
-
// Skip nested groups with no visible children
|
|
757
794
|
if (!hasVisibleChildren(child)) return null;
|
|
758
|
-
|
|
759
|
-
return (
|
|
760
|
-
<div
|
|
761
|
-
key={`nested-group-${childKey || childIndex}`}
|
|
762
|
-
className={cn(child.subtle ? 'mt-1' : 'ml-3 mt-1.5 pl-3 border-l border-[rgb(var(--ec-content-border))]')}
|
|
763
|
-
>
|
|
764
|
-
{renderGroup(child, childKey, childIndex)}
|
|
765
|
-
</div>
|
|
766
|
-
);
|
|
795
|
+
return renderGroup(child, childKey, childIndex, depth + 1);
|
|
767
796
|
}
|
|
768
|
-
|
|
769
|
-
//
|
|
770
|
-
// per-
|
|
771
|
-
return renderItem(child, childKey, childIndex,
|
|
797
|
+
|
|
798
|
+
// Under a nested group the header already conveys the resource type, so the
|
|
799
|
+
// default per-collection glyph is dropped; custom icons still show.
|
|
800
|
+
return renderItem(child, childKey, childIndex, isNested);
|
|
772
801
|
})}
|
|
773
802
|
</div>
|
|
774
803
|
)}
|
|
@@ -845,7 +874,7 @@ export default function NestedSideBar() {
|
|
|
845
874
|
);
|
|
846
875
|
|
|
847
876
|
const baseClasses =
|
|
848
|
-
'group flex items-center justify-between w-full px-
|
|
877
|
+
'group flex items-center justify-between w-full px-2 py-1.5 rounded-md cursor-pointer text-left transition-colors hover:bg-[rgb(var(--ec-content-hover))] active:bg-[rgb(var(--ec-content-hover))]';
|
|
849
878
|
const parentClasses = itemHasChildren ? 'font-medium' : '';
|
|
850
879
|
const activeClasses = isActive ? 'bg-[rgb(var(--ec-rail-active-bg))] hover:bg-[rgb(var(--ec-rail-active-bg))]' : '';
|
|
851
880
|
|
|
@@ -857,6 +886,8 @@ export default function NestedSideBar() {
|
|
|
857
886
|
href={item.href}
|
|
858
887
|
title={item.title}
|
|
859
888
|
target={item.external ? '_blank' : undefined}
|
|
889
|
+
aria-current={isActive ? 'page' : undefined}
|
|
890
|
+
data-active={isActive ? 'true' : undefined}
|
|
860
891
|
className={cn(baseClasses, parentClasses, activeClasses)}
|
|
861
892
|
>
|
|
862
893
|
{content}
|
|
@@ -870,6 +901,7 @@ export default function NestedSideBar() {
|
|
|
870
901
|
key={`item-${itemKey || index}`}
|
|
871
902
|
title={item.title}
|
|
872
903
|
onClick={() => handleDrillDown(item, itemKey)}
|
|
904
|
+
data-active={isActive ? 'true' : undefined}
|
|
873
905
|
className={cn(baseClasses, parentClasses, activeClasses)}
|
|
874
906
|
>
|
|
875
907
|
{content}
|
|
@@ -1040,6 +1072,7 @@ export default function NestedSideBar() {
|
|
|
1040
1072
|
{/* Navigation Content */}
|
|
1041
1073
|
<nav
|
|
1042
1074
|
key={animationKey}
|
|
1075
|
+
ref={navRef}
|
|
1043
1076
|
className={cn('min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-4 px-2', getAnimationClass())}
|
|
1044
1077
|
style={{
|
|
1045
1078
|
scrollbarWidth: 'thin',
|