@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.
@@ -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, X } from 'lucide-react';
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 and unselected → select; open and
37
- * selected → close + clear focus. The right-aligned close button collapses
38
- * without selecting. A step row focuses that step's edge. */
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 [closeHover, setCloseHover] = useState(false);
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
- setCloseHover(false);
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
- onFocusFlow(walkthrough);
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={`Close ${walkthrough.title}`}
239
- onMouseEnter={() => setCloseHover(true)}
240
- onMouseLeave={() => setCloseHover(false)}
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: closeHover ? theme.colors.border : 'transparent',
257
- color: closeHover ? theme.colors.text : muted,
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
- <X size={12} strokeWidth={2} />
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 12px',
341
+ padding: '8px 8px 8px 17px',
323
342
  textAlign: 'left',
324
- borderRadius: 6,
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
- color: stepActive ? theme.colors.text : muted,
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
- boxShadow: showTop
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
  );