@principal-ai/subsystems-react 0.28.1 → 0.28.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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +7 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +4 -201
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/WalkthroughsPanel.d.ts +47 -0
- package/dist/subsystem/WalkthroughsPanel.d.ts.map +1 -0
- package/dist/subsystem/WalkthroughsPanel.js +345 -0
- package/dist/subsystem/WalkthroughsPanel.js.map +1 -0
- package/dist/subsystem/model.d.ts +15 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +28 -0
- package/dist/subsystem/model.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +204 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +29 -319
- package/src/subsystem/WalkthroughsPanel.tsx +569 -0
- package/src/subsystem/model.test.ts +76 -1
- package/src/subsystem/model.ts +31 -0
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
applyNodeChanges,
|
|
33
33
|
} from '@xyflow/react';
|
|
34
34
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
35
|
-
import { FileText,
|
|
35
|
+
import { FileText, X } from 'lucide-react';
|
|
36
36
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
37
37
|
import {
|
|
38
38
|
buildSubsystemGraph,
|
|
@@ -63,14 +63,13 @@ import { ComponentDeclaration } from './ComponentDeclaration';
|
|
|
63
63
|
import type { ComponentVerificationState } from './ComponentDeclaration';
|
|
64
64
|
import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
|
|
65
65
|
import { buildRepoGroups, repoAvatarUrl, type RepoGroup } from './paths';
|
|
66
|
+
import { WalkthroughsPanel, WALKTHROUGH_PLAY_PAUSE_MS } from './WalkthroughsPanel';
|
|
66
67
|
|
|
67
68
|
/** Cap screen-space edge labels to this fraction of the edge's on-screen length. */
|
|
68
69
|
const EDGE_LABEL_MAX_EDGE_FRACTION = 0.55;
|
|
69
70
|
/** Rough monospace width at fontSize 10 + horizontal padding/border. */
|
|
70
71
|
const EDGE_LABEL_CHAR_PX = 6.2;
|
|
71
72
|
const EDGE_LABEL_PAD_PX = 18;
|
|
72
|
-
/** Pause (ms) between steps when a walkthrough autoplays. */
|
|
73
|
-
const WALKTHROUGH_PLAY_PAUSE_MS = 2500;
|
|
74
73
|
|
|
75
74
|
/** Context passed to `renderWalkthroughViewer` when a flow/step is focused. */
|
|
76
75
|
export interface WalkthroughViewerContext {
|
|
@@ -107,6 +106,13 @@ export interface SubsystemComponentGraphProps {
|
|
|
107
106
|
* graph.
|
|
108
107
|
*/
|
|
109
108
|
initialWalkthroughId?: string | null;
|
|
109
|
+
/**
|
|
110
|
+
* Called with the next walkthrough order after a drag in the sidebar's
|
|
111
|
+
* flows panel. When set, each row grows a drag grip and a drop emits the
|
|
112
|
+
* reordered array — the host owns persisting it (the graph stays controlled
|
|
113
|
+
* and never reorders its own prop).
|
|
114
|
+
*/
|
|
115
|
+
onReorderWalkthroughs?: (next: SubsystemWalkthrough[]) => void;
|
|
110
116
|
onSelect?: (componentAlias: string) => void;
|
|
111
117
|
/** Called when an edge is clicked (relationship / mechanism + refs seam). */
|
|
112
118
|
onEdgeSelect?: (edge: SubsystemComponentEdge) => void;
|
|
@@ -313,7 +319,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
|
|
|
313
319
|
measured: { w: number; h: number } | null;
|
|
314
320
|
}
|
|
315
321
|
|
|
316
|
-
function Inner({ components, relations, walkthroughs, initialWalkthroughId, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification }: InnerProps) {
|
|
322
|
+
function Inner({ components, relations, walkthroughs, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification }: InnerProps) {
|
|
317
323
|
const { theme } = useTheme();
|
|
318
324
|
const { fitView } = useReactFlow();
|
|
319
325
|
const viewport = useViewport();
|
|
@@ -1684,40 +1690,25 @@ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onSe
|
|
|
1684
1690
|
</div>
|
|
1685
1691
|
)}
|
|
1686
1692
|
{sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (
|
|
1687
|
-
<
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
{
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1707
|
-
onToggleCollapsed={toggleWalkthroughCollapsed}
|
|
1708
|
-
onFocusFlow={focusWalkthroughEdges}
|
|
1709
|
-
onClearFocus={clearWalkthroughFocus}
|
|
1710
|
-
onFocusStep={focusWalkthroughStep}
|
|
1711
|
-
onHoverStep={(tl, i) =>
|
|
1712
|
-
setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i })
|
|
1713
|
-
}
|
|
1714
|
-
onHoverFlow={(tl) =>
|
|
1715
|
-
setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null })
|
|
1716
|
-
}
|
|
1717
|
-
onLeaveStep={() => setHoveredWalkthroughStep(null)}
|
|
1718
|
-
/>
|
|
1719
|
-
))}
|
|
1720
|
-
</div>
|
|
1693
|
+
<WalkthroughsPanel
|
|
1694
|
+
walkthroughs={walkthroughs}
|
|
1695
|
+
expandedWalkthroughs={expandedWalkthroughs}
|
|
1696
|
+
focusedWalkthroughId={focusedWalkthroughId}
|
|
1697
|
+
focusedStepIndex={focusedStepIndex}
|
|
1698
|
+
hoveredWalkthroughStep={hoveredWalkthroughStep}
|
|
1699
|
+
onToggleCollapsed={toggleWalkthroughCollapsed}
|
|
1700
|
+
onFocusFlow={focusWalkthroughEdges}
|
|
1701
|
+
onClearFocus={clearWalkthroughFocus}
|
|
1702
|
+
onFocusStep={focusWalkthroughStep}
|
|
1703
|
+
onHoverStep={(tl, i) =>
|
|
1704
|
+
setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i })
|
|
1705
|
+
}
|
|
1706
|
+
onHoverFlow={(tl) =>
|
|
1707
|
+
setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null })
|
|
1708
|
+
}
|
|
1709
|
+
onLeaveStep={() => setHoveredWalkthroughStep(null)}
|
|
1710
|
+
onReorder={onReorderWalkthroughs}
|
|
1711
|
+
/>
|
|
1721
1712
|
) : treeFilePaths.length > 0 ? (
|
|
1722
1713
|
<>
|
|
1723
1714
|
{repoGroups.groups.map((group, i) => {
|
|
@@ -2216,287 +2207,6 @@ function RepoGroupHeader({
|
|
|
2216
2207
|
);
|
|
2217
2208
|
}
|
|
2218
2209
|
|
|
2219
|
-
/** One collapsible walkthrough in the sidebar's flows panel. Clicking the
|
|
2220
|
-
* title: closed → open + select; open and unselected → select; open and
|
|
2221
|
-
* selected → close + clear focus. The right-aligned close button collapses
|
|
2222
|
-
* without selecting. A step row focuses that step's edge. */
|
|
2223
|
-
function WalkthroughFlow({
|
|
2224
|
-
walkthrough,
|
|
2225
|
-
collapsed,
|
|
2226
|
-
active,
|
|
2227
|
-
onToggleCollapsed,
|
|
2228
|
-
onFocusFlow,
|
|
2229
|
-
onClearFocus,
|
|
2230
|
-
onFocusStep,
|
|
2231
|
-
onHoverStep,
|
|
2232
|
-
onHoverFlow,
|
|
2233
|
-
onLeaveStep,
|
|
2234
|
-
}: {
|
|
2235
|
-
walkthrough: SubsystemWalkthrough;
|
|
2236
|
-
collapsed: boolean;
|
|
2237
|
-
/** `{ stepIndex: null }` = whole flow focused; `{ stepIndex }` = one step. */
|
|
2238
|
-
active: { stepIndex: number | null } | null;
|
|
2239
|
-
onToggleCollapsed: (tlId: string) => void;
|
|
2240
|
-
onFocusFlow: (tl: SubsystemWalkthrough) => void;
|
|
2241
|
-
onClearFocus: () => void;
|
|
2242
|
-
onFocusStep: (tl: SubsystemWalkthrough, stepIndex: number) => void;
|
|
2243
|
-
onHoverStep: (tl: SubsystemWalkthrough, stepIndex: number) => void;
|
|
2244
|
-
/** Preview the whole flow on the canvas (used while the row is collapsed). */
|
|
2245
|
-
onHoverFlow: (tl: SubsystemWalkthrough) => void;
|
|
2246
|
-
onLeaveStep: () => void;
|
|
2247
|
-
}) {
|
|
2248
|
-
const { theme } = useTheme();
|
|
2249
|
-
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
2250
|
-
const hoverBg = theme.colors.background;
|
|
2251
|
-
const wholeFlowActive = active !== null && active.stepIndex === null;
|
|
2252
|
-
const [headerHover, setHeaderHover] = useState(false);
|
|
2253
|
-
const [closeHover, setCloseHover] = useState(false);
|
|
2254
|
-
const [playHover, setPlayHover] = useState(false);
|
|
2255
|
-
const [hoveredStep, setHoveredStep] = useState<number | null>(null);
|
|
2256
|
-
const stepButtonRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
2257
|
-
// Autoplay: stepping through the flow's steps with a pause between each.
|
|
2258
|
-
const [playing, setPlaying] = useState(false);
|
|
2259
|
-
const playTimerRef = useRef<number | null>(null);
|
|
2260
|
-
|
|
2261
|
-
const stopPlaying = useCallback(() => {
|
|
2262
|
-
if (playTimerRef.current != null) {
|
|
2263
|
-
window.clearTimeout(playTimerRef.current);
|
|
2264
|
-
playTimerRef.current = null;
|
|
2265
|
-
}
|
|
2266
|
-
setPlaying(false);
|
|
2267
|
-
}, []);
|
|
2268
|
-
|
|
2269
|
-
// Clear any pending timer on unmount.
|
|
2270
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2271
|
-
useEffect(() => () => stopPlaying(), []);
|
|
2272
|
-
|
|
2273
|
-
const startPlaying = useCallback(() => {
|
|
2274
|
-
if (collapsed) onToggleCollapsed(walkthrough.id);
|
|
2275
|
-
if (active === null || active.stepIndex !== null) onFocusFlow(walkthrough);
|
|
2276
|
-
const stepCount = walkthrough.steps.length;
|
|
2277
|
-
if (stepCount === 0) return;
|
|
2278
|
-
setPlaying(true);
|
|
2279
|
-
let i = 0;
|
|
2280
|
-
const tick = () => {
|
|
2281
|
-
if (i >= stepCount) {
|
|
2282
|
-
playTimerRef.current = null;
|
|
2283
|
-
setPlaying(false);
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
onFocusStep(walkthrough, i);
|
|
2287
|
-
i += 1;
|
|
2288
|
-
playTimerRef.current = window.setTimeout(tick, WALKTHROUGH_PLAY_PAUSE_MS);
|
|
2289
|
-
};
|
|
2290
|
-
tick();
|
|
2291
|
-
}, [collapsed, active, onToggleCollapsed, onFocusFlow, walkthrough, onFocusStep]);
|
|
2292
|
-
|
|
2293
|
-
const togglePlay = useCallback(() => {
|
|
2294
|
-
if (playing) {
|
|
2295
|
-
stopPlaying();
|
|
2296
|
-
} else {
|
|
2297
|
-
startPlaying();
|
|
2298
|
-
}
|
|
2299
|
-
}, [playing, stopPlaying, startPlaying]);
|
|
2300
|
-
|
|
2301
|
-
// Keep DOM focus on the active step so the browser focus ring (and
|
|
2302
|
-
// subsequent arrow keys) follow arrow navigation, not the originally
|
|
2303
|
-
// clicked button.
|
|
2304
|
-
useEffect(() => {
|
|
2305
|
-
if (active?.stepIndex == null) return;
|
|
2306
|
-
stepButtonRefs.current[active.stepIndex]?.focus({ preventScroll: true });
|
|
2307
|
-
}, [active?.stepIndex]);
|
|
2308
|
-
|
|
2309
|
-
return (
|
|
2310
|
-
<div>
|
|
2311
|
-
<div
|
|
2312
|
-
onMouseEnter={() => {
|
|
2313
|
-
setHeaderHover(true);
|
|
2314
|
-
if (collapsed) onHoverFlow(walkthrough);
|
|
2315
|
-
}}
|
|
2316
|
-
onMouseLeave={() => {
|
|
2317
|
-
setHeaderHover(false);
|
|
2318
|
-
if (collapsed) onLeaveStep();
|
|
2319
|
-
}}
|
|
2320
|
-
style={{
|
|
2321
|
-
display: 'flex',
|
|
2322
|
-
alignItems: 'center',
|
|
2323
|
-
gap: 4,
|
|
2324
|
-
padding: '0 16px',
|
|
2325
|
-
background: wholeFlowActive || headerHover ? hoverBg : 'transparent',
|
|
2326
|
-
transition: 'background 120ms ease',
|
|
2327
|
-
}}
|
|
2328
|
-
>
|
|
2329
|
-
<button
|
|
2330
|
-
type="button"
|
|
2331
|
-
onClick={() => {
|
|
2332
|
-
if (collapsed) {
|
|
2333
|
-
onToggleCollapsed(walkthrough.id);
|
|
2334
|
-
onFocusFlow(walkthrough);
|
|
2335
|
-
} else if (active === null) {
|
|
2336
|
-
onFocusFlow(walkthrough);
|
|
2337
|
-
} else {
|
|
2338
|
-
onToggleCollapsed(walkthrough.id);
|
|
2339
|
-
onClearFocus();
|
|
2340
|
-
}
|
|
2341
|
-
}}
|
|
2342
|
-
style={{
|
|
2343
|
-
flex: 1,
|
|
2344
|
-
display: 'flex',
|
|
2345
|
-
alignItems: 'center',
|
|
2346
|
-
minWidth: 0,
|
|
2347
|
-
padding: '10px 0',
|
|
2348
|
-
border: 'none',
|
|
2349
|
-
background: 'transparent',
|
|
2350
|
-
textAlign: 'left',
|
|
2351
|
-
cursor: 'pointer',
|
|
2352
|
-
}}
|
|
2353
|
-
>
|
|
2354
|
-
<span
|
|
2355
|
-
style={{
|
|
2356
|
-
overflow: 'hidden',
|
|
2357
|
-
textOverflow: 'ellipsis',
|
|
2358
|
-
whiteSpace: 'nowrap',
|
|
2359
|
-
fontSize: theme.fontSizes[1],
|
|
2360
|
-
fontFamily: theme.fonts.monospace,
|
|
2361
|
-
fontWeight: 600,
|
|
2362
|
-
color: theme.colors.text,
|
|
2363
|
-
}}
|
|
2364
|
-
>
|
|
2365
|
-
{walkthrough.title}
|
|
2366
|
-
</span>
|
|
2367
|
-
</button>
|
|
2368
|
-
{!collapsed && (
|
|
2369
|
-
<>
|
|
2370
|
-
<button
|
|
2371
|
-
type="button"
|
|
2372
|
-
aria-label={playing ? `Pause ${walkthrough.title} autoplay` : `Play ${walkthrough.title}`}
|
|
2373
|
-
title={playing ? 'Pause' : 'Play through steps'}
|
|
2374
|
-
onMouseEnter={() => setPlayHover(true)}
|
|
2375
|
-
onMouseLeave={() => setPlayHover(false)}
|
|
2376
|
-
onClick={(e) => {
|
|
2377
|
-
e.stopPropagation();
|
|
2378
|
-
togglePlay();
|
|
2379
|
-
}}
|
|
2380
|
-
style={{
|
|
2381
|
-
display: 'inline-flex',
|
|
2382
|
-
alignItems: 'center',
|
|
2383
|
-
justifyContent: 'center',
|
|
2384
|
-
flexShrink: 0,
|
|
2385
|
-
width: 22,
|
|
2386
|
-
height: 22,
|
|
2387
|
-
padding: 0,
|
|
2388
|
-
border: 'none',
|
|
2389
|
-
borderRadius: 4,
|
|
2390
|
-
background: playing || playHover ? theme.colors.border : 'transparent',
|
|
2391
|
-
color: playing || playHover ? theme.colors.text : muted,
|
|
2392
|
-
cursor: 'pointer',
|
|
2393
|
-
transition: 'background 120ms ease, color 120ms ease',
|
|
2394
|
-
}}
|
|
2395
|
-
>
|
|
2396
|
-
{playing ? <Pause size={12} strokeWidth={2} /> : <Play size={12} strokeWidth={2} />}
|
|
2397
|
-
</button>
|
|
2398
|
-
<button
|
|
2399
|
-
type="button"
|
|
2400
|
-
aria-label={`Close ${walkthrough.title}`}
|
|
2401
|
-
onMouseEnter={() => setCloseHover(true)}
|
|
2402
|
-
onMouseLeave={() => setCloseHover(false)}
|
|
2403
|
-
onClick={(e) => {
|
|
2404
|
-
e.stopPropagation();
|
|
2405
|
-
onToggleCollapsed(walkthrough.id);
|
|
2406
|
-
if (active !== null) onClearFocus();
|
|
2407
|
-
}}
|
|
2408
|
-
style={{
|
|
2409
|
-
display: 'inline-flex',
|
|
2410
|
-
alignItems: 'center',
|
|
2411
|
-
justifyContent: 'center',
|
|
2412
|
-
flexShrink: 0,
|
|
2413
|
-
width: 22,
|
|
2414
|
-
height: 22,
|
|
2415
|
-
padding: 0,
|
|
2416
|
-
border: 'none',
|
|
2417
|
-
borderRadius: 4,
|
|
2418
|
-
background: closeHover ? theme.colors.border : 'transparent',
|
|
2419
|
-
color: closeHover ? theme.colors.text : muted,
|
|
2420
|
-
cursor: 'pointer',
|
|
2421
|
-
transition: 'background 120ms ease, color 120ms ease',
|
|
2422
|
-
}}
|
|
2423
|
-
>
|
|
2424
|
-
<X size={12} strokeWidth={2} />
|
|
2425
|
-
</button>
|
|
2426
|
-
</>
|
|
2427
|
-
)}
|
|
2428
|
-
</div>
|
|
2429
|
-
{!collapsed && (
|
|
2430
|
-
<div
|
|
2431
|
-
style={{ display: 'flex', flexDirection: 'column' }}
|
|
2432
|
-
onMouseLeave={() => {
|
|
2433
|
-
setHoveredStep(null);
|
|
2434
|
-
onLeaveStep();
|
|
2435
|
-
}}
|
|
2436
|
-
>
|
|
2437
|
-
{walkthrough.steps.map((step, i) => {
|
|
2438
|
-
const stepActive = active !== null && active.stepIndex === i;
|
|
2439
|
-
return (
|
|
2440
|
-
<button
|
|
2441
|
-
key={`${walkthroughStepGraphEdgeId(step)}-${i}`}
|
|
2442
|
-
ref={(el) => {
|
|
2443
|
-
stepButtonRefs.current[i] = el;
|
|
2444
|
-
}}
|
|
2445
|
-
type="button"
|
|
2446
|
-
onMouseEnter={() => {
|
|
2447
|
-
setHoveredStep(i);
|
|
2448
|
-
onHoverStep(walkthrough, i);
|
|
2449
|
-
}}
|
|
2450
|
-
onClick={() => onFocusStep(walkthrough, i)}
|
|
2451
|
-
style={{
|
|
2452
|
-
display: 'flex',
|
|
2453
|
-
alignItems: 'center',
|
|
2454
|
-
gap: 8,
|
|
2455
|
-
minWidth: 0,
|
|
2456
|
-
padding: '8px 8px 8px 12px',
|
|
2457
|
-
textAlign: 'left',
|
|
2458
|
-
borderRadius: 6,
|
|
2459
|
-
border: 'none',
|
|
2460
|
-
outline: 'none',
|
|
2461
|
-
background: stepActive || hoveredStep === i ? hoverBg : 'transparent',
|
|
2462
|
-
cursor: 'pointer',
|
|
2463
|
-
transition: 'background 120ms ease',
|
|
2464
|
-
}}
|
|
2465
|
-
>
|
|
2466
|
-
<span
|
|
2467
|
-
style={{
|
|
2468
|
-
flexShrink: 0,
|
|
2469
|
-
width: 14,
|
|
2470
|
-
fontSize: theme.fontSizes[0],
|
|
2471
|
-
fontFamily: theme.fonts.monospace,
|
|
2472
|
-
color: stepActive ? theme.colors.text : muted,
|
|
2473
|
-
}}
|
|
2474
|
-
>
|
|
2475
|
-
{i + 1}
|
|
2476
|
-
</span>
|
|
2477
|
-
<span
|
|
2478
|
-
style={{
|
|
2479
|
-
flex: 1,
|
|
2480
|
-
minWidth: 0,
|
|
2481
|
-
overflow: 'hidden',
|
|
2482
|
-
textOverflow: 'ellipsis',
|
|
2483
|
-
whiteSpace: 'nowrap',
|
|
2484
|
-
fontSize: theme.fontSizes[1],
|
|
2485
|
-
fontFamily: theme.fonts.monospace,
|
|
2486
|
-
color: theme.colors.text,
|
|
2487
|
-
}}
|
|
2488
|
-
>
|
|
2489
|
-
{step.symbol}
|
|
2490
|
-
</span>
|
|
2491
|
-
</button>
|
|
2492
|
-
);
|
|
2493
|
-
})}
|
|
2494
|
-
</div>
|
|
2495
|
-
)}
|
|
2496
|
-
</div>
|
|
2497
|
-
);
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
2210
|
export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
|
|
2501
2211
|
const wrapRef = useRef<HTMLDivElement>(null);
|
|
2502
2212
|
const [size, setSize] = useState<{ w: number; h: number } | null>(null);
|