@principal-ai/subsystems-react 0.37.0 → 0.37.2
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +16 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +75 -7
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +13 -3
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +273 -220
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +36 -28
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.d.ts.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.js +1 -1
- package/dist/subsystem/SymbolInspectionCard.js.map +1 -1
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +7 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/utils/edgeLabel.d.ts +51 -0
- package/dist/utils/edgeLabel.d.ts.map +1 -0
- package/dist/utils/edgeLabel.js +81 -0
- package/dist/utils/edgeLabel.js.map +1 -0
- package/dist/utils/elkLayout.d.ts +6 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +27 -6
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +457 -0
- package/src/subsystem/ComponentDeclaration.tsx +130 -7
- package/src/subsystem/ConstructsCatalog.tsx +431 -341
- package/src/subsystem/SubsystemComponentGraph.tsx +74 -29
- package/src/subsystem/SymbolInspectionCard.tsx +10 -9
- package/src/subsystem/model.ts +7 -1
- package/src/utils/edgeLabel.test.ts +23 -0
- package/src/utils/edgeLabel.ts +95 -0
- package/src/utils/elkLayout.ts +41 -6
|
@@ -3,15 +3,21 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* ConstructsCatalog — master/detail for a constructs-only subsystem
|
|
4
4
|
* (components, no topology or walkthrough edges).
|
|
5
5
|
*
|
|
6
|
-
* Left: the model's
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Left: the model's files as a tree; clicking a file toggles its
|
|
7
|
+
* constructs. Right: signatures grouped by repo, and within a repo stacked
|
|
8
|
+
* per file under one combined file header carrying the path and a
|
|
9
|
+
* description toggle; a declaration with a known line labels just that
|
|
10
|
+
* construct's line in a file-like gutter, and `external` constructs get a
|
|
11
|
+
* header naming their kind with the body naming the construct. The model
|
|
12
|
+
* description's toggle sits in the left chrome and opens the markdown as an
|
|
13
|
+
* overlay over the construct area. File opens still use the bottom FileDrawer
|
|
14
|
+
* when the host injects a viewer.
|
|
9
15
|
*/
|
|
10
16
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
17
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
12
18
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
13
|
-
import { ChevronDown, ChevronRight, FileText, Folder } from 'lucide-react';
|
|
14
|
-
import {
|
|
19
|
+
import { AlignLeft, Box, ChevronDown, ChevronRight, FileText, Folder, X, } from 'lucide-react';
|
|
20
|
+
import { formatPurl, } from './model';
|
|
15
21
|
import { ComponentDeclaration } from './ComponentDeclaration';
|
|
16
22
|
import { SubsystemDiagnosticToggle } from './DiagnosticToggle';
|
|
17
23
|
import { FileDrawer } from './FileDrawer';
|
|
@@ -81,6 +87,24 @@ function repoLogo(c) {
|
|
|
81
87
|
return `https://github.com/${m[1]}.png?size=40`;
|
|
82
88
|
return undefined;
|
|
83
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Split a repo's declarations into consecutive runs that share a `file`, so
|
|
92
|
+
* each run can render as one connected stack. Components with no `file` are
|
|
93
|
+
* never merged (they have no shared location to group under).
|
|
94
|
+
*/
|
|
95
|
+
function splitByFileRun(items) {
|
|
96
|
+
const runs = [];
|
|
97
|
+
for (const c of items) {
|
|
98
|
+
const prev = runs[runs.length - 1];
|
|
99
|
+
const key = c.file || `alias:${c.alias}`;
|
|
100
|
+
const prevKey = prev && prev[0] ? prev[0].file || `alias:${prev[0].alias}` : null;
|
|
101
|
+
if (prev && prevKey === key)
|
|
102
|
+
prev.push(c);
|
|
103
|
+
else
|
|
104
|
+
runs.push([c]);
|
|
105
|
+
}
|
|
106
|
+
return runs;
|
|
107
|
+
}
|
|
84
108
|
export function ConstructsCatalog({ components, onSelect, title, hideSidebar, description, diagnostic, sidebarExtra, sidebarAfterDescription, renderFileViewer, renderFileView, onFileSelect, componentVerification, onInspectSymbol, }) {
|
|
85
109
|
const { theme, mode } = useTheme();
|
|
86
110
|
const pierreTheme = resolvePierreSyntaxThemeName(mode);
|
|
@@ -104,27 +128,52 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
104
128
|
})
|
|
105
129
|
.map((x) => x.c);
|
|
106
130
|
}, [components]);
|
|
107
|
-
const [visibleAliases, setVisibleAliases] = useState(
|
|
108
|
-
const [focusedAlias, setFocusedAlias] = useState(
|
|
131
|
+
const [visibleAliases, setVisibleAliases] = useState([]);
|
|
132
|
+
const [focusedAlias, setFocusedAlias] = useState(null);
|
|
133
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
109
134
|
const [descriptionVisible, setDescriptionVisible] = useState(false);
|
|
110
135
|
const [descToggleHover, setDescToggleHover] = useState(false);
|
|
111
136
|
const [hoveredRow, setHoveredRow] = useState(null);
|
|
112
|
-
const [sidebarTab, setSidebarTab] = useState('files');
|
|
113
137
|
const [collapsedFolders, setCollapsedFolders] = useState(new Set());
|
|
138
|
+
// Explicit description toggles per file run. Unset falls back to the kind's
|
|
139
|
+
// default (externals open, files closed).
|
|
140
|
+
const [purposeOverrides, setPurposeOverrides] = useState({});
|
|
114
141
|
const [drawer, setDrawer] = useState(null);
|
|
115
|
-
const focusedAliasRef = useRef(focusedAlias);
|
|
116
|
-
focusedAliasRef.current = focusedAlias;
|
|
117
142
|
const visibleSet = useMemo(() => new Set(visibleAliases), [visibleAliases]);
|
|
118
143
|
const visibleComponents = useMemo(() => orderedComponents.filter((c) => visibleSet.has(c.alias)), [orderedComponents, visibleSet]);
|
|
144
|
+
// When nothing is toggled on, the right pane falls back to a searchable list
|
|
145
|
+
// of every construct, rendered as if all were selected.
|
|
146
|
+
const searchActive = visibleComponents.length === 0;
|
|
147
|
+
const searchResults = useMemo(() => {
|
|
148
|
+
if (!searchActive)
|
|
149
|
+
return visibleComponents;
|
|
150
|
+
const q = searchQuery.trim().toLowerCase();
|
|
151
|
+
if (!q)
|
|
152
|
+
return orderedComponents;
|
|
153
|
+
return orderedComponents.filter((c) => {
|
|
154
|
+
const haystack = [
|
|
155
|
+
c.name,
|
|
156
|
+
c.symbol,
|
|
157
|
+
c.alias,
|
|
158
|
+
c.file,
|
|
159
|
+
c.construct,
|
|
160
|
+
c.stereotype,
|
|
161
|
+
c.purpose,
|
|
162
|
+
]
|
|
163
|
+
.filter(Boolean)
|
|
164
|
+
.join(' ')
|
|
165
|
+
.toLowerCase();
|
|
166
|
+
return haystack.includes(q);
|
|
167
|
+
});
|
|
168
|
+
}, [searchActive, searchQuery, orderedComponents, visibleComponents]);
|
|
119
169
|
const fileTree = useMemo(() => buildFileTree(orderedComponents), [orderedComponents]);
|
|
120
170
|
const hasFiles = useMemo(() => orderedComponents.some((c) => !!c.file), [orderedComponents]);
|
|
121
|
-
|
|
122
|
-
//
|
|
123
|
-
// renders once per group instead of on every card.
|
|
171
|
+
// Group the declarations on screen by purl so the repo/app identity
|
|
172
|
+
// (logo + name) renders once per group instead of on every card.
|
|
124
173
|
const repoGroups = useMemo(() => {
|
|
125
174
|
const groups = [];
|
|
126
175
|
const index = new Map();
|
|
127
|
-
for (const c of
|
|
176
|
+
for (const c of searchResults) {
|
|
128
177
|
const key = c.purl || c.alias;
|
|
129
178
|
let at = index.get(key);
|
|
130
179
|
if (at == null) {
|
|
@@ -137,12 +186,7 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
137
186
|
groups[at].items.push(c);
|
|
138
187
|
}
|
|
139
188
|
return groups;
|
|
140
|
-
}, [
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (!focusedAlias)
|
|
143
|
-
return;
|
|
144
|
-
document.getElementById(`construct-${focusedAlias}`)?.scrollIntoView({ block: 'nearest' });
|
|
145
|
-
}, [focusedAlias]);
|
|
189
|
+
}, [searchResults]);
|
|
146
190
|
useEffect(() => {
|
|
147
191
|
const last = visibleAliases[visibleAliases.length - 1];
|
|
148
192
|
if (!last)
|
|
@@ -163,34 +207,17 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
163
207
|
.getElementById(`construct-signature-${match.alias}`)
|
|
164
208
|
?.scrollIntoView({ block: 'nearest' });
|
|
165
209
|
}, [orderedComponents, drawer]);
|
|
166
|
-
// Drop aliases that left the model;
|
|
210
|
+
// Drop aliases that left the model; an empty set is valid (search fallback).
|
|
167
211
|
useEffect(() => {
|
|
168
212
|
const aliases = new Set(orderedComponents.map((c) => c.alias));
|
|
169
|
-
setVisibleAliases((prev) =>
|
|
170
|
-
|
|
171
|
-
if (next.length > 0 || orderedComponents.length === 0)
|
|
172
|
-
return next;
|
|
173
|
-
return [orderedComponents[0].alias];
|
|
174
|
-
});
|
|
175
|
-
setFocusedAlias((prev) => {
|
|
176
|
-
if (prev && aliases.has(prev))
|
|
177
|
-
return prev;
|
|
178
|
-
return orderedComponents[0]?.alias ?? null;
|
|
179
|
-
});
|
|
213
|
+
setVisibleAliases((prev) => prev.filter((alias) => aliases.has(alias)));
|
|
214
|
+
setFocusedAlias((prev) => (prev && aliases.has(prev) ? prev : null));
|
|
180
215
|
}, [orderedComponents]);
|
|
181
|
-
const toggle = useCallback((alias) => {
|
|
182
|
-
setFocusedAlias(alias);
|
|
183
|
-
setVisibleAliases((prev) => prev.includes(alias) ? prev.filter((x) => x !== alias) : [...prev, alias]);
|
|
184
|
-
onSelect?.(alias);
|
|
185
|
-
}, [onSelect]);
|
|
186
216
|
const show = useCallback((alias) => {
|
|
187
217
|
setFocusedAlias(alias);
|
|
188
218
|
setVisibleAliases((prev) => (prev.includes(alias) ? prev : [...prev, alias]));
|
|
189
219
|
onSelect?.(alias);
|
|
190
220
|
}, [onSelect]);
|
|
191
|
-
const focusAt = useCallback((alias) => {
|
|
192
|
-
setFocusedAlias(alias);
|
|
193
|
-
}, []);
|
|
194
221
|
const toggleFolder = useCallback((path) => {
|
|
195
222
|
setCollapsedFolders((prev) => {
|
|
196
223
|
const next = new Set(prev);
|
|
@@ -201,8 +228,11 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
201
228
|
return next;
|
|
202
229
|
});
|
|
203
230
|
}, []);
|
|
231
|
+
const setPurposeOpen = useCallback((key, open) => {
|
|
232
|
+
setPurposeOverrides((prev) => ({ ...prev, [key]: open }));
|
|
233
|
+
}, []);
|
|
204
234
|
/** File-tree click: show every construct declared in the file, or hide them
|
|
205
|
-
* all when they're already shown —
|
|
235
|
+
* all when they're already shown — a bulk toggle of the file's constructs. */
|
|
206
236
|
const toggleFile = useCallback((comps) => {
|
|
207
237
|
const aliases = comps.map((c) => c.alias);
|
|
208
238
|
setVisibleAliases((prev) => {
|
|
@@ -307,8 +337,17 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
307
337
|
const comp = resolveRelated(components, ref);
|
|
308
338
|
if (!comp)
|
|
309
339
|
return;
|
|
340
|
+
// In the all-constructs search fallback nothing needs revealing — just
|
|
341
|
+
// surface the target. Otherwise add it to the visible set.
|
|
342
|
+
if (searchActive) {
|
|
343
|
+
setFocusedAlias(comp.alias);
|
|
344
|
+
document
|
|
345
|
+
.getElementById(`construct-signature-${comp.alias}`)
|
|
346
|
+
?.scrollIntoView({ block: 'nearest' });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
310
349
|
show(comp.alias);
|
|
311
|
-
}, [components, show]);
|
|
350
|
+
}, [components, show, searchActive]);
|
|
312
351
|
const fileViewer = useMemo(() => {
|
|
313
352
|
if (renderFileViewer)
|
|
314
353
|
return renderFileViewer;
|
|
@@ -324,60 +363,20 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
324
363
|
const fileViewerRef = useRef(fileViewer);
|
|
325
364
|
fileViewerRef.current = fileViewer;
|
|
326
365
|
const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
|
|
327
|
-
const moveFocus = useCallback((delta) => {
|
|
328
|
-
if (orderedComponents.length === 0)
|
|
329
|
-
return;
|
|
330
|
-
const idx = Math.max(0, orderedComponents.findIndex((c) => c.alias === focusedAliasRef.current));
|
|
331
|
-
const next = orderedComponents[(idx + delta + orderedComponents.length) % orderedComponents.length];
|
|
332
|
-
focusAt(next.alias);
|
|
333
|
-
}, [orderedComponents, focusAt]);
|
|
334
366
|
useEffect(() => {
|
|
335
367
|
const onKey = (e) => {
|
|
336
|
-
|
|
337
|
-
if (el &&
|
|
338
|
-
(el.tagName === 'INPUT' ||
|
|
339
|
-
el.tagName === 'TEXTAREA' ||
|
|
340
|
-
el.tagName === 'SELECT' ||
|
|
341
|
-
el.isContentEditable)) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (e.metaKey || e.ctrlKey || e.altKey)
|
|
345
|
-
return;
|
|
346
|
-
if (e.key === 'ArrowDown') {
|
|
347
|
-
e.preventDefault();
|
|
348
|
-
moveFocus(1);
|
|
349
|
-
}
|
|
350
|
-
else if (e.key === 'ArrowUp') {
|
|
351
|
-
e.preventDefault();
|
|
352
|
-
moveFocus(-1);
|
|
353
|
-
}
|
|
354
|
-
else if (e.key === 'Home') {
|
|
355
|
-
e.preventDefault();
|
|
356
|
-
if (orderedComponents[0])
|
|
357
|
-
focusAt(orderedComponents[0].alias);
|
|
358
|
-
}
|
|
359
|
-
else if (e.key === 'End') {
|
|
360
|
-
e.preventDefault();
|
|
361
|
-
const last = orderedComponents[orderedComponents.length - 1];
|
|
362
|
-
if (last)
|
|
363
|
-
focusAt(last.alias);
|
|
364
|
-
}
|
|
365
|
-
else if (e.key === ' ' || e.key === 'Enter') {
|
|
366
|
-
if (focusedAliasRef.current) {
|
|
367
|
-
e.preventDefault();
|
|
368
|
-
toggle(focusedAliasRef.current);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
else if (e.key === 'Escape' && drawer) {
|
|
368
|
+
if (e.key === 'Escape' && drawer)
|
|
372
369
|
setDrawer(null);
|
|
373
|
-
}
|
|
374
370
|
};
|
|
375
371
|
window.addEventListener('keydown', onKey);
|
|
376
372
|
return () => window.removeEventListener('keydown', onKey);
|
|
377
|
-
}, [
|
|
373
|
+
}, [drawer]);
|
|
378
374
|
const showChrome = !hideSidebar &&
|
|
379
|
-
!!(title ||
|
|
380
|
-
|
|
375
|
+
!!(title ||
|
|
376
|
+
description ||
|
|
377
|
+
diagnostic ||
|
|
378
|
+
sidebarExtra ||
|
|
379
|
+
sidebarAfterDescription);
|
|
381
380
|
return (_jsxs("div", { "data-testid": "constructs-catalog", style: {
|
|
382
381
|
width: '100%',
|
|
383
382
|
height: '100%',
|
|
@@ -396,7 +395,6 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
396
395
|
flexDirection: 'column',
|
|
397
396
|
overflow: 'hidden',
|
|
398
397
|
}, children: [showChrome && (_jsxs("div", { style: {
|
|
399
|
-
flex: showDesc ? '0 0 auto' : undefined,
|
|
400
398
|
padding: '16px 16px 8px',
|
|
401
399
|
display: 'flex',
|
|
402
400
|
flexDirection: 'column',
|
|
@@ -428,125 +426,88 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
428
426
|
background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
|
|
429
427
|
color: descriptionVisible || descToggleHover ? theme.colors.text : muted,
|
|
430
428
|
cursor: 'pointer',
|
|
431
|
-
}, children: _jsx(FileText, { size: 14 }) }))] })] })),
|
|
432
|
-
display: 'flex',
|
|
433
|
-
width: '100%',
|
|
434
|
-
flexShrink: 0,
|
|
435
|
-
borderBottom: `1px solid ${theme.colors.border}`,
|
|
436
|
-
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
437
|
-
}, children: ['files', 'constructs'].map((tab) => {
|
|
438
|
-
if (tab === 'files' && !hasFiles)
|
|
439
|
-
return null;
|
|
440
|
-
const active = effectiveTab === tab;
|
|
441
|
-
return (_jsx("button", { type: "button", role: "tab", "aria-selected": active, onClick: () => setSidebarTab(tab), style: {
|
|
442
|
-
flex: 1,
|
|
443
|
-
minWidth: 0,
|
|
444
|
-
padding: '8px 8px',
|
|
445
|
-
border: 'none',
|
|
446
|
-
borderRadius: 0,
|
|
447
|
-
background: active
|
|
448
|
-
? theme.colors.background
|
|
449
|
-
: 'transparent',
|
|
450
|
-
color: active ? theme.colors.text : muted,
|
|
451
|
-
fontSize: theme.fontSizes[0],
|
|
452
|
-
fontFamily: theme.fonts.monospace,
|
|
453
|
-
letterSpacing: 0.4,
|
|
454
|
-
textTransform: 'uppercase',
|
|
455
|
-
cursor: 'pointer',
|
|
456
|
-
}, children: tab === 'constructs' ? 'Constructs' : 'Files' }, tab));
|
|
457
|
-
}) }), effectiveTab === 'constructs' ? (_jsx("ul", { role: "listbox", "aria-label": "Constructs", "aria-multiselectable": "true", "aria-activedescendant": focusedAlias ? `construct-${focusedAlias}` : undefined, style: {
|
|
458
|
-
listStyle: 'none',
|
|
459
|
-
margin: 0,
|
|
460
|
-
padding: '0 8px 12px',
|
|
461
|
-
overflowY: 'auto',
|
|
462
|
-
flex: 1,
|
|
463
|
-
minHeight: 0,
|
|
464
|
-
}, children: orderedComponents.map((c) => {
|
|
465
|
-
const visible = visibleSet.has(c.alias);
|
|
466
|
-
const focused = c.alias === focusedAlias;
|
|
467
|
-
const color = componentColor(c, pierreTheme);
|
|
468
|
-
const badgeColor = constructBadgeColor(c) ?? color;
|
|
469
|
-
const badgeLabel = constructBadgeLabel(c);
|
|
470
|
-
const displayName = deriveNameFromSymbol(c.symbol, c.construct, c.name, c.file, c.stereotype);
|
|
471
|
-
const rowOpen = drawer != null &&
|
|
472
|
-
drawer.startLine != null &&
|
|
473
|
-
!!c.file &&
|
|
474
|
-
c.file === drawer.file &&
|
|
475
|
-
c.declarationRef?.startLine === drawer.startLine;
|
|
476
|
-
const hovered = hoveredRow === c.alias;
|
|
477
|
-
return (_jsx("li", { role: "presentation", children: _jsxs("button", { type: "button", id: `construct-${c.alias}`, role: "option", "aria-selected": visible, "data-testid": `construct-row-${c.alias}`, onClick: () => toggle(c.alias), onMouseEnter: () => setHoveredRow(c.alias), onMouseLeave: () => setHoveredRow((h) => (h === c.alias ? null : h)), style: {
|
|
478
|
-
display: 'flex',
|
|
479
|
-
flexDirection: 'column',
|
|
480
|
-
alignItems: 'stretch',
|
|
481
|
-
gap: 2,
|
|
482
|
-
width: '100%',
|
|
483
|
-
margin: '2px 0',
|
|
484
|
-
padding: '8px 10px 8px 12px',
|
|
485
|
-
textAlign: 'left',
|
|
486
|
-
border: 'none',
|
|
487
|
-
borderRadius: 6,
|
|
488
|
-
background: hovered
|
|
489
|
-
? theme.colors.border
|
|
490
|
-
: visible
|
|
491
|
-
? theme.colors.background
|
|
492
|
-
: 'transparent',
|
|
493
|
-
cursor: 'pointer',
|
|
494
|
-
transition: 'background 100ms ease, opacity 100ms ease',
|
|
495
|
-
boxShadow: [
|
|
496
|
-
visible ? `inset 0 0 0 1px ${theme.colors.border}` : null,
|
|
497
|
-
rowOpen ? `inset 2px 0 0 ${accentColor}` : null,
|
|
498
|
-
]
|
|
499
|
-
.filter(Boolean)
|
|
500
|
-
.join(', ') || undefined,
|
|
501
|
-
outline: focused && !visible ? `1px dotted ${muted}` : undefined,
|
|
502
|
-
outlineOffset: -1,
|
|
503
|
-
opacity: visible ? 1 : hovered ? 1 : 0.72,
|
|
504
|
-
}, children: [_jsx("span", { style: {
|
|
505
|
-
display: 'flex',
|
|
506
|
-
alignItems: 'baseline',
|
|
507
|
-
gap: 8,
|
|
508
|
-
minWidth: 0,
|
|
509
|
-
}, children: _jsx("span", { style: {
|
|
510
|
-
flex: 1,
|
|
511
|
-
minWidth: 0,
|
|
512
|
-
overflow: 'hidden',
|
|
513
|
-
textOverflow: 'ellipsis',
|
|
514
|
-
whiteSpace: 'nowrap',
|
|
515
|
-
fontFamily: theme.fonts.monospace,
|
|
516
|
-
fontSize: theme.fontSizes[1],
|
|
517
|
-
color,
|
|
518
|
-
fontWeight: visible ? 600 : 500,
|
|
519
|
-
}, children: displayName }) }), badgeLabel && (_jsx("span", { style: {
|
|
520
|
-
display: 'flex',
|
|
521
|
-
alignItems: 'center',
|
|
522
|
-
minWidth: 0,
|
|
523
|
-
}, children: _jsx("span", { style: {
|
|
524
|
-
flex: 1,
|
|
525
|
-
minWidth: 0,
|
|
526
|
-
overflow: 'hidden',
|
|
527
|
-
textOverflow: 'ellipsis',
|
|
528
|
-
whiteSpace: 'nowrap',
|
|
529
|
-
fontFamily: theme.fonts.monospace,
|
|
530
|
-
fontSize: theme.fontSizes[0],
|
|
531
|
-
letterSpacing: 0.4,
|
|
532
|
-
textTransform: 'uppercase',
|
|
533
|
-
color: badgeColor,
|
|
534
|
-
}, children: badgeLabel }) }))] }) }, c.alias));
|
|
535
|
-
}) })) : (_jsx("ul", { role: "tree", "aria-label": "Files", style: {
|
|
429
|
+
}, children: _jsx(FileText, { size: 14 }) }))] })] })), sidebarAfterDescription] })), hasFiles ? (_jsx("ul", { role: "tree", "aria-label": "Files", style: {
|
|
536
430
|
listStyle: 'none',
|
|
537
431
|
margin: 0,
|
|
538
432
|
padding: '6px 8px 12px',
|
|
539
433
|
overflowY: 'auto',
|
|
540
434
|
flex: 1,
|
|
541
435
|
minHeight: 0,
|
|
542
|
-
}, children: renderTree(fileTree, 0) }))
|
|
436
|
+
}, children: renderTree(fileTree, 0) })) : (_jsx("p", { style: {
|
|
437
|
+
margin: 0,
|
|
438
|
+
padding: '12px 16px',
|
|
439
|
+
color: muted,
|
|
440
|
+
fontFamily: theme.fonts.body,
|
|
441
|
+
fontSize: theme.fontSizes[0],
|
|
442
|
+
}, children: "No files in this model." }))] }), _jsxs("div", { style: {
|
|
543
443
|
position: 'relative',
|
|
544
444
|
flex: 1,
|
|
545
445
|
minWidth: 0,
|
|
546
446
|
minHeight: 0,
|
|
547
447
|
display: 'flex',
|
|
548
448
|
flexDirection: 'column',
|
|
549
|
-
}, children: [_jsx("div", {
|
|
449
|
+
}, children: [description && descriptionVisible && (_jsxs(_Fragment, { children: [_jsx("div", { onClick: () => setDescriptionVisible(false), style: {
|
|
450
|
+
position: 'absolute',
|
|
451
|
+
inset: 0,
|
|
452
|
+
zIndex: 2,
|
|
453
|
+
background: 'rgba(0,0,0,0.45)',
|
|
454
|
+
} }), _jsxs("div", { style: {
|
|
455
|
+
position: 'absolute',
|
|
456
|
+
top: '50%',
|
|
457
|
+
left: '50%',
|
|
458
|
+
transform: 'translate(-50%, -50%)',
|
|
459
|
+
zIndex: 3,
|
|
460
|
+
width: 360,
|
|
461
|
+
maxWidth: 'calc(100% - 16px)',
|
|
462
|
+
maxHeight: '70%',
|
|
463
|
+
overflow: 'auto',
|
|
464
|
+
padding: 16,
|
|
465
|
+
border: `1px solid ${theme.colors.border}`,
|
|
466
|
+
borderRadius: 8,
|
|
467
|
+
background: theme.colors.background,
|
|
468
|
+
boxShadow: '0 8px 24px rgba(0,0,0,0.28)',
|
|
469
|
+
fontSize: theme.fontSizes[0],
|
|
470
|
+
lineHeight: 1.5,
|
|
471
|
+
}, children: [_jsxs("div", { style: {
|
|
472
|
+
display: 'flex',
|
|
473
|
+
alignItems: 'center',
|
|
474
|
+
gap: 8,
|
|
475
|
+
marginBottom: 12,
|
|
476
|
+
}, children: [_jsx("h3", { style: {
|
|
477
|
+
margin: 0,
|
|
478
|
+
flex: 1,
|
|
479
|
+
minWidth: 0,
|
|
480
|
+
fontSize: theme.fontSizes[1],
|
|
481
|
+
fontWeight: 600,
|
|
482
|
+
fontFamily: theme.fonts.monospace,
|
|
483
|
+
letterSpacing: 0.4,
|
|
484
|
+
textTransform: 'uppercase',
|
|
485
|
+
color: theme.colors.textSecondary ?? muted,
|
|
486
|
+
}, children: "Overview" }), _jsx("button", { type: "button", "aria-label": "Close description", title: "Close", onClick: () => setDescriptionVisible(false), style: {
|
|
487
|
+
display: 'inline-flex',
|
|
488
|
+
alignItems: 'center',
|
|
489
|
+
justifyContent: 'center',
|
|
490
|
+
flexShrink: 0,
|
|
491
|
+
width: 22,
|
|
492
|
+
height: 22,
|
|
493
|
+
padding: 0,
|
|
494
|
+
border: 'none',
|
|
495
|
+
borderRadius: 4,
|
|
496
|
+
background: 'transparent',
|
|
497
|
+
color: muted,
|
|
498
|
+
cursor: 'pointer',
|
|
499
|
+
}, children: _jsx(X, { size: 14 }) })] }), _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "constructs-desc-overlay", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false })] })] })), searchActive && (_jsx("div", { style: { flexShrink: 0, padding: '12px 16px 0' }, children: _jsx("input", { type: "search", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search constructs\u2026", "aria-label": "Search constructs", "data-testid": "construct-search", style: {
|
|
500
|
+
width: '100%',
|
|
501
|
+
boxSizing: 'border-box',
|
|
502
|
+
padding: '8px 10px',
|
|
503
|
+
border: `1px solid ${theme.colors.border}`,
|
|
504
|
+
borderRadius: 6,
|
|
505
|
+
background: theme.colors.background,
|
|
506
|
+
color: theme.colors.text,
|
|
507
|
+
fontFamily: theme.fonts.monospace,
|
|
508
|
+
fontSize: theme.fontSizes[1],
|
|
509
|
+
outline: 'none',
|
|
510
|
+
} }) })), _jsx("div", { "data-testid": "construct-signature", style: {
|
|
550
511
|
flex: 1,
|
|
551
512
|
minHeight: 0,
|
|
552
513
|
overflow: 'auto',
|
|
@@ -554,7 +515,7 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
554
515
|
display: 'flex',
|
|
555
516
|
flexDirection: 'column',
|
|
556
517
|
gap: 12,
|
|
557
|
-
}, children:
|
|
518
|
+
}, children: searchResults.length > 0 ? (repoGroups.map((group) => (_jsxs("div", { style: {
|
|
558
519
|
display: 'flex',
|
|
559
520
|
flexDirection: 'column',
|
|
560
521
|
gap: 12,
|
|
@@ -565,35 +526,127 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
565
526
|
color: theme.colors.textSecondary ?? muted,
|
|
566
527
|
fontFamily: theme.fonts.body,
|
|
567
528
|
fontSize: theme.fontSizes[1],
|
|
568
|
-
}, children: group.label })] }), group.items.map((
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
529
|
+
}, children: group.label })] }), splitByFileRun(group.items).map((run) => {
|
|
530
|
+
const lead = run[0];
|
|
531
|
+
const hasFile = !!lead.file;
|
|
532
|
+
const isExternal = lead.construct === 'external';
|
|
533
|
+
// File-backed runs and externals both get a header; externals
|
|
534
|
+
// name their kind instead of a path, so the body doesn't have
|
|
535
|
+
// to spell it out.
|
|
536
|
+
const showHeader = hasFile || isExternal;
|
|
537
|
+
const runKey = `${group.key}:${lead.file || lead.alias}`;
|
|
538
|
+
const runHasPurpose = run.some((c) => !!c.purpose?.trim());
|
|
539
|
+
const purposeOpen = purposeOverrides[runKey] ?? isExternal;
|
|
540
|
+
const runFileOpen = drawer != null && hasFile && lead.file === drawer.file;
|
|
541
|
+
return (_jsxs("div", { style: {
|
|
542
|
+
display: 'flex',
|
|
543
|
+
flexDirection: 'column',
|
|
544
|
+
border: `1px solid ${runFileOpen ? accentColor : theme.colors.border}`,
|
|
579
545
|
borderRadius: 8,
|
|
580
546
|
overflow: 'hidden',
|
|
581
547
|
flexShrink: 0,
|
|
582
|
-
}, children:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
548
|
+
}, children: [showHeader && (_jsxs("div", { style: {
|
|
549
|
+
display: 'flex',
|
|
550
|
+
alignItems: 'center',
|
|
551
|
+
gap: 6,
|
|
552
|
+
width: '100%',
|
|
553
|
+
boxSizing: 'border-box',
|
|
554
|
+
padding: '6px 10px',
|
|
555
|
+
borderBottom: `1px solid ${runFileOpen ? accentColor : theme.colors.border}`,
|
|
556
|
+
background: runFileOpen
|
|
557
|
+
? `${accentColor}14`
|
|
558
|
+
: theme.colors.backgroundSecondary ??
|
|
559
|
+
theme.colors.background,
|
|
560
|
+
color: runFileOpen
|
|
561
|
+
? accentColor
|
|
562
|
+
: theme.colors.textSecondary ?? muted,
|
|
563
|
+
fontFamily: theme.fonts.monospace,
|
|
564
|
+
fontSize: theme.fontSizes[0],
|
|
565
|
+
}, children: [isExternal ? (_jsxs("span", { style: {
|
|
566
|
+
display: 'flex',
|
|
567
|
+
alignItems: 'center',
|
|
568
|
+
gap: 6,
|
|
569
|
+
flex: 1,
|
|
570
|
+
minWidth: 0,
|
|
571
|
+
}, children: [_jsx(Box, { size: 12, style: { flexShrink: 0 } }), _jsx("span", { style: {
|
|
572
|
+
overflow: 'hidden',
|
|
573
|
+
textOverflow: 'ellipsis',
|
|
574
|
+
whiteSpace: 'nowrap',
|
|
575
|
+
}, children: "external" })] })) : (_jsxs("button", { type: "button", title: fileViewer ? `Open ${lead.file}` : lead.file, onClick: fileViewer
|
|
576
|
+
? () => onOpenFile(lead.file)
|
|
577
|
+
: undefined, style: {
|
|
578
|
+
display: 'flex',
|
|
579
|
+
alignItems: 'center',
|
|
580
|
+
gap: 6,
|
|
581
|
+
flex: 1,
|
|
582
|
+
minWidth: 0,
|
|
583
|
+
padding: 0,
|
|
584
|
+
border: 'none',
|
|
585
|
+
background: 'transparent',
|
|
586
|
+
color: 'inherit',
|
|
587
|
+
fontFamily: 'inherit',
|
|
588
|
+
fontSize: 'inherit',
|
|
589
|
+
textAlign: 'left',
|
|
590
|
+
cursor: fileViewer ? 'pointer' : 'default',
|
|
591
|
+
}, children: [_jsx(FileText, { size: 12, style: { flexShrink: 0 } }), _jsx("span", { style: {
|
|
592
|
+
overflow: 'hidden',
|
|
593
|
+
textOverflow: 'ellipsis',
|
|
594
|
+
whiteSpace: 'nowrap',
|
|
595
|
+
}, children: lead.file })] })), runHasPurpose && (_jsx("button", { type: "button", title: purposeOpen ? 'Hide description' : 'Show description', "aria-label": purposeOpen ? 'Hide description' : 'Show description', "aria-expanded": purposeOpen, onClick: () => setPurposeOpen(runKey, !purposeOpen), style: {
|
|
596
|
+
display: 'inline-flex',
|
|
597
|
+
alignItems: 'center',
|
|
598
|
+
justifyContent: 'center',
|
|
599
|
+
flexShrink: 0,
|
|
600
|
+
width: 20,
|
|
601
|
+
height: 20,
|
|
602
|
+
padding: 0,
|
|
603
|
+
border: 'none',
|
|
604
|
+
borderRadius: 4,
|
|
605
|
+
background: 'transparent',
|
|
606
|
+
color: purposeOpen
|
|
607
|
+
? accentColor
|
|
608
|
+
: theme.colors.textSecondary ?? muted,
|
|
609
|
+
cursor: 'pointer',
|
|
610
|
+
}, children: _jsx(AlignLeft, { size: 13 }) }))] })), run.map((c, i) => {
|
|
611
|
+
// File-level: every declaration sharing the open file
|
|
612
|
+
// shows the indicator. Line-level: only the declaration
|
|
613
|
+
// actually highlighted gets the inner accent ring.
|
|
614
|
+
const fileOpen = drawer != null && !!c.file && c.file === drawer.file;
|
|
615
|
+
const lineOpen = fileOpen &&
|
|
616
|
+
drawer.startLine != null &&
|
|
617
|
+
c.declarationRef?.startLine === drawer.startLine;
|
|
618
|
+
// A combined header owns the path, so declarations under
|
|
619
|
+
// it drop their own badge; those with no known line just
|
|
620
|
+
// render unnumbered. Without a header, keep the badge.
|
|
621
|
+
const numbered = hasFile && c.declarationRef?.startLine != null;
|
|
622
|
+
// The header names the external kind, so the body just
|
|
623
|
+
// names the construct.
|
|
624
|
+
const isExt = c.construct === 'external';
|
|
625
|
+
return (_jsx("div", { id: `construct-signature-${c.alias}`, style: {
|
|
626
|
+
borderTop: i > 0
|
|
627
|
+
? `1px solid ${theme.colors.border}`
|
|
628
|
+
: undefined,
|
|
629
|
+
boxShadow: lineOpen
|
|
630
|
+
? `inset 0 0 0 1px ${accentColor}`
|
|
631
|
+
: undefined,
|
|
632
|
+
}, children: _jsx(ComponentDeclaration, { component: c, onOpenFile: fileViewer ? onOpenFile : undefined, defaultShowFile: showHeader ? false : !!fileViewer, onRelatedSelect: onRelatedSelect, onInspectSymbol: onInspectSymbol
|
|
633
|
+
? (symbol, ref) => onInspectSymbol({
|
|
634
|
+
purl: c.purl,
|
|
635
|
+
file: c.file,
|
|
636
|
+
symbol,
|
|
637
|
+
ref,
|
|
638
|
+
})
|
|
639
|
+
: undefined, fileOpen: fileOpen, declarationOpen: lineOpen, showRepoIdentity: false, fileBadgeChrome: showHeader ? false : !!fileViewer, hideActions: showHeader, externalName: isExt, showPurpose: showHeader ? purposeOpen : undefined, lineNumbers: numbered, verification: componentVerification && focusedAlias === c.alias
|
|
640
|
+
? componentVerification
|
|
641
|
+
: undefined }) }, c.alias));
|
|
642
|
+
})] }, runKey));
|
|
592
643
|
})] }, group.key)))) : (_jsx("p", { style: {
|
|
593
644
|
margin: 0,
|
|
594
645
|
color: muted,
|
|
595
646
|
fontFamily: theme.fonts.body,
|
|
596
647
|
fontSize: theme.fontSizes[1],
|
|
597
|
-
}, children:
|
|
648
|
+
}, children: searchQuery.trim()
|
|
649
|
+
? `No constructs match “${searchQuery.trim()}”.`
|
|
650
|
+
: 'No constructs in this model.' })) }), _jsx(FileDrawer, { title: drawer?.file ?? null, onClose: () => setDrawer(null), fillHeight: drawer != null && drawer.startLine == null, children: drawer ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawer.file, startLine: drawer.startLine })) : null })] })] }));
|
|
598
651
|
}
|
|
599
652
|
//# sourceMappingURL=ConstructsCatalog.js.map
|