@principal-ai/subsystems-react 0.28.2 → 0.28.4
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/subsystem/SubsystemComponentGraph.d.ts +7 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +71 -9
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/WalkthroughsPanel.d.ts +7 -1
- package/dist/subsystem/WalkthroughsPanel.d.ts.map +1 -1
- package/dist/subsystem/WalkthroughsPanel.js +68 -42
- package/dist/subsystem/WalkthroughsPanel.js.map +1 -1
- package/package.json +1 -1
- package/src/stories/Subsystem/ComponentGraph/ProposedWalkthroughs.stories.tsx +165 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +98 -8
- package/src/subsystem/WalkthroughsPanel.tsx +85 -26
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
14
|
import type { DragEvent as ReactDragEvent } from 'react';
|
|
15
15
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
16
|
-
import { GripVertical, Pause, Play
|
|
16
|
+
import { ChevronDown, GripVertical, Pause, Play } from 'lucide-react';
|
|
17
17
|
import {
|
|
18
|
+
PROPOSED_COLOR,
|
|
18
19
|
reorderTargetIndex,
|
|
19
20
|
reorderWalkthroughs,
|
|
20
21
|
walkthroughStepGraphEdgeId,
|
|
@@ -33,9 +34,10 @@ interface WalkthroughReorderHandle {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/** One collapsible walkthrough in the sidebar's flows panel. Clicking the
|
|
36
|
-
* title: closed → open + select; open
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* title: closed → open + select; open with a step selected → select the whole
|
|
38
|
+
* flow (deselect the step, stay open); open with the whole flow selected →
|
|
39
|
+
* close + clear focus. The right-aligned close button collapses without
|
|
40
|
+
* selecting. A step row focuses that step's edge. */
|
|
39
41
|
function WalkthroughFlow({
|
|
40
42
|
walkthrough,
|
|
41
43
|
collapsed,
|
|
@@ -50,6 +52,7 @@ function WalkthroughFlow({
|
|
|
50
52
|
reorder,
|
|
51
53
|
dragActive,
|
|
52
54
|
isHoverSuppressed,
|
|
55
|
+
proposedAliases,
|
|
53
56
|
}: {
|
|
54
57
|
walkthrough: SubsystemWalkthrough;
|
|
55
58
|
collapsed: boolean;
|
|
@@ -69,13 +72,23 @@ function WalkthroughFlow({
|
|
|
69
72
|
dragActive?: boolean;
|
|
70
73
|
/** True just after a drop; ignores the browser's synthetic hover until the pointer moves. */
|
|
71
74
|
isHoverSuppressed?: () => boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Aliases of components marked `proposed`. A row whose steps touch one gets
|
|
77
|
+
* its title tinted, and each such step (index and title) is tinted too
|
|
78
|
+
* (darkgoldenrod, same as the proposed node treatment).
|
|
79
|
+
*/
|
|
80
|
+
proposedAliases?: ReadonlySet<string>;
|
|
72
81
|
}) {
|
|
73
82
|
const { theme } = useTheme();
|
|
74
83
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
75
84
|
const hoverBg = theme.colors.background;
|
|
76
85
|
const wholeFlowActive = active !== null && active.stepIndex === null;
|
|
86
|
+
// A step is "proposed" when either endpoint is a proposed component.
|
|
87
|
+
const stepProposed = (from: string, to: string) =>
|
|
88
|
+
proposedAliases != null && (proposedAliases.has(from) || proposedAliases.has(to));
|
|
89
|
+
const touchesProposed = walkthrough.steps.some((s) => stepProposed(s.from, s.to));
|
|
77
90
|
const [headerHover, setHeaderHover] = useState(false);
|
|
78
|
-
const [
|
|
91
|
+
const [collapseHover, setCollapseHover] = useState(false);
|
|
79
92
|
const [playHover, setPlayHover] = useState(false);
|
|
80
93
|
const [gripHover, setGripHover] = useState(false);
|
|
81
94
|
const [hoveredStep, setHoveredStep] = useState<number | null>(null);
|
|
@@ -101,7 +114,7 @@ function WalkthroughFlow({
|
|
|
101
114
|
// drop. Reset every row's hover whenever a drag starts or ends.
|
|
102
115
|
useEffect(() => {
|
|
103
116
|
setHeaderHover(false);
|
|
104
|
-
|
|
117
|
+
setCollapseHover(false);
|
|
105
118
|
setPlayHover(false);
|
|
106
119
|
setGripHover(false);
|
|
107
120
|
setHoveredStep(null);
|
|
@@ -170,11 +183,14 @@ function WalkthroughFlow({
|
|
|
170
183
|
if (collapsed) {
|
|
171
184
|
onToggleCollapsed(walkthrough.id);
|
|
172
185
|
onFocusFlow(walkthrough);
|
|
173
|
-
} else if (active === null) {
|
|
174
|
-
|
|
175
|
-
} else {
|
|
186
|
+
} else if (active !== null && active.stepIndex === null) {
|
|
187
|
+
// Whole flow already selected → collapse + clear.
|
|
176
188
|
onToggleCollapsed(walkthrough.id);
|
|
177
189
|
onClearFocus();
|
|
190
|
+
} else {
|
|
191
|
+
// Expanded with nothing (or a step) selected → select the whole
|
|
192
|
+
// flow, which deselects any focused step without collapsing.
|
|
193
|
+
onFocusFlow(walkthrough);
|
|
178
194
|
}
|
|
179
195
|
}}
|
|
180
196
|
style={{
|
|
@@ -197,7 +213,7 @@ function WalkthroughFlow({
|
|
|
197
213
|
fontSize: theme.fontSizes[1],
|
|
198
214
|
fontFamily: theme.fonts.monospace,
|
|
199
215
|
fontWeight: 600,
|
|
200
|
-
color: theme.colors.text,
|
|
216
|
+
color: touchesProposed ? PROPOSED_COLOR : theme.colors.text,
|
|
201
217
|
}}
|
|
202
218
|
>
|
|
203
219
|
{walkthrough.title}
|
|
@@ -235,9 +251,10 @@ function WalkthroughFlow({
|
|
|
235
251
|
</button>
|
|
236
252
|
<button
|
|
237
253
|
type="button"
|
|
238
|
-
aria-label={`
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
aria-label={`Collapse ${walkthrough.title}`}
|
|
255
|
+
title="Collapse"
|
|
256
|
+
onMouseEnter={() => setCollapseHover(true)}
|
|
257
|
+
onMouseLeave={() => setCollapseHover(false)}
|
|
241
258
|
onClick={(e) => {
|
|
242
259
|
e.stopPropagation();
|
|
243
260
|
onToggleCollapsed(walkthrough.id);
|
|
@@ -253,13 +270,13 @@ function WalkthroughFlow({
|
|
|
253
270
|
padding: 0,
|
|
254
271
|
border: 'none',
|
|
255
272
|
borderRadius: 4,
|
|
256
|
-
background:
|
|
257
|
-
color:
|
|
273
|
+
background: collapseHover ? theme.colors.border : 'transparent',
|
|
274
|
+
color: collapseHover ? theme.colors.text : muted,
|
|
258
275
|
cursor: 'pointer',
|
|
259
276
|
transition: 'background 120ms ease, color 120ms ease',
|
|
260
277
|
}}
|
|
261
278
|
>
|
|
262
|
-
<
|
|
279
|
+
<ChevronDown size={14} strokeWidth={2} />
|
|
263
280
|
</button>
|
|
264
281
|
</>
|
|
265
282
|
)}
|
|
@@ -302,6 +319,7 @@ function WalkthroughFlow({
|
|
|
302
319
|
>
|
|
303
320
|
{walkthrough.steps.map((step, i) => {
|
|
304
321
|
const stepActive = active !== null && active.stepIndex === i;
|
|
322
|
+
const proposed = stepProposed(step.from, step.to);
|
|
305
323
|
return (
|
|
306
324
|
<button
|
|
307
325
|
key={`${walkthroughStepGraphEdgeId(step)}-${i}`}
|
|
@@ -309,6 +327,7 @@ function WalkthroughFlow({
|
|
|
309
327
|
stepButtonRefs.current[i] = el;
|
|
310
328
|
}}
|
|
311
329
|
type="button"
|
|
330
|
+
title={proposed ? 'Step touches a proposed component' : undefined}
|
|
312
331
|
onMouseEnter={() => {
|
|
313
332
|
setHoveredStep(i);
|
|
314
333
|
onHoverStep(walkthrough, i);
|
|
@@ -319,9 +338,9 @@ function WalkthroughFlow({
|
|
|
319
338
|
alignItems: 'center',
|
|
320
339
|
gap: 8,
|
|
321
340
|
minWidth: 0,
|
|
322
|
-
padding: '8px 8px 8px
|
|
341
|
+
padding: '8px 8px 8px 17px',
|
|
323
342
|
textAlign: 'left',
|
|
324
|
-
borderRadius:
|
|
343
|
+
borderRadius: 0,
|
|
325
344
|
border: 'none',
|
|
326
345
|
outline: 'none',
|
|
327
346
|
background: stepActive || hoveredStep === i ? hoverBg : 'transparent',
|
|
@@ -335,7 +354,12 @@ function WalkthroughFlow({
|
|
|
335
354
|
width: 14,
|
|
336
355
|
fontSize: theme.fontSizes[0],
|
|
337
356
|
fontFamily: theme.fonts.monospace,
|
|
338
|
-
|
|
357
|
+
fontWeight: proposed ? 700 : undefined,
|
|
358
|
+
color: proposed
|
|
359
|
+
? PROPOSED_COLOR
|
|
360
|
+
: stepActive
|
|
361
|
+
? theme.colors.text
|
|
362
|
+
: muted,
|
|
339
363
|
}}
|
|
340
364
|
>
|
|
341
365
|
{i + 1}
|
|
@@ -349,7 +373,7 @@ function WalkthroughFlow({
|
|
|
349
373
|
whiteSpace: 'nowrap',
|
|
350
374
|
fontSize: theme.fontSizes[1],
|
|
351
375
|
fontFamily: theme.fonts.monospace,
|
|
352
|
-
color: theme.colors.text,
|
|
376
|
+
color: proposed ? PROPOSED_COLOR : theme.colors.text,
|
|
353
377
|
}}
|
|
354
378
|
>
|
|
355
379
|
{step.symbol}
|
|
@@ -387,6 +411,12 @@ export interface WalkthroughsPanelProps {
|
|
|
387
411
|
* for a read-only panel.
|
|
388
412
|
*/
|
|
389
413
|
onReorder?: (next: SubsystemWalkthrough[]) => void;
|
|
414
|
+
/**
|
|
415
|
+
* Aliases of components marked `proposed`. A row whose steps touch one gets
|
|
416
|
+
* its title tinted, and each such step (index and title) is tinted too
|
|
417
|
+
* (darkgoldenrod, same as the proposed node treatment).
|
|
418
|
+
*/
|
|
419
|
+
proposedAliases?: ReadonlySet<string>;
|
|
390
420
|
}
|
|
391
421
|
|
|
392
422
|
/**
|
|
@@ -408,6 +438,7 @@ export function WalkthroughsPanel({
|
|
|
408
438
|
onHoverFlow,
|
|
409
439
|
onLeaveStep,
|
|
410
440
|
onReorder,
|
|
441
|
+
proposedAliases,
|
|
411
442
|
}: WalkthroughsPanelProps) {
|
|
412
443
|
const { theme } = useTheme();
|
|
413
444
|
// Reordering is offered only while every row is collapsed: the compact list
|
|
@@ -522,15 +553,42 @@ export function WalkthroughsPanel({
|
|
|
522
553
|
onDragOver={reorderable ? (e) => handleDragOver(e, i) : undefined}
|
|
523
554
|
onDrop={reorderable ? handleDrop : undefined}
|
|
524
555
|
style={{
|
|
556
|
+
position: 'relative',
|
|
525
557
|
opacity: isDragging ? 0.45 : 1,
|
|
526
|
-
|
|
527
|
-
? `inset 0 2px 0 ${accent}`
|
|
528
|
-
: showBottom
|
|
529
|
-
? `inset 0 -2px 0 ${accent}`
|
|
530
|
-
: undefined,
|
|
531
|
-
transition: 'opacity 120ms ease, box-shadow 120ms ease',
|
|
558
|
+
transition: 'opacity 120ms ease',
|
|
532
559
|
}}
|
|
533
560
|
>
|
|
561
|
+
{/* Overlays so the divider/drop line stay visible above the row's
|
|
562
|
+
own (opaque) hover background. Accent paints over the divider. */}
|
|
563
|
+
{i > 0 && (
|
|
564
|
+
<div
|
|
565
|
+
aria-hidden
|
|
566
|
+
style={{
|
|
567
|
+
position: 'absolute',
|
|
568
|
+
top: 0,
|
|
569
|
+
left: 0,
|
|
570
|
+
right: 0,
|
|
571
|
+
height: 1,
|
|
572
|
+
background: theme.colors.border,
|
|
573
|
+
pointerEvents: 'none',
|
|
574
|
+
}}
|
|
575
|
+
/>
|
|
576
|
+
)}
|
|
577
|
+
{(showTop || showBottom) && (
|
|
578
|
+
<div
|
|
579
|
+
aria-hidden
|
|
580
|
+
style={{
|
|
581
|
+
position: 'absolute',
|
|
582
|
+
left: 0,
|
|
583
|
+
right: 0,
|
|
584
|
+
height: 2,
|
|
585
|
+
zIndex: 2,
|
|
586
|
+
background: accent,
|
|
587
|
+
pointerEvents: 'none',
|
|
588
|
+
...(showTop ? { top: 0 } : { bottom: 0 }),
|
|
589
|
+
}}
|
|
590
|
+
/>
|
|
591
|
+
)}
|
|
534
592
|
<WalkthroughFlow
|
|
535
593
|
walkthrough={tl}
|
|
536
594
|
collapsed={!expandedWalkthroughs.has(tl.id)}
|
|
@@ -560,6 +618,7 @@ export function WalkthroughsPanel({
|
|
|
560
618
|
}
|
|
561
619
|
dragActive={dragIndex != null}
|
|
562
620
|
isHoverSuppressed={isHoverSuppressed}
|
|
621
|
+
proposedAliases={proposedAliases}
|
|
563
622
|
/>
|
|
564
623
|
</div>
|
|
565
624
|
);
|