Skip to content

Commit 75f9df5

Browse files
kubeclaude
andauthored
FE-570: Replace SVG simulation timeline with uPlot (#8627)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent efbcf7c commit 75f9df5

7 files changed

Lines changed: 853 additions & 1190 deletions

File tree

libs/@hashintel/petrinaut/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@
4444
"@hashintel/refractive": "workspace:^",
4545
"@monaco-editor/react": "4.8.0-rc.3",
4646
"@xyflow/react": "12.10.1",
47-
"d3-scale": "4.0.2",
4847
"elkjs": "0.11.0",
4948
"fuzzysort": "3.1.0",
5049
"lodash-es": "4.18.1",
5150
"monaco-editor": "0.55.1",
5251
"react-icons": "5.5.0",
5352
"react-resizable-panels": "4.6.5",
5453
"typescript": "5.9.3",
54+
"uplot": "1.6.32",
5555
"uuid": "13.0.0",
5656
"vscode-languageserver-types": "3.17.5",
5757
"web-worker": "1.4.1",
@@ -66,7 +66,6 @@
6666
"@testing-library/dom": "10.4.1",
6767
"@testing-library/react": "16.3.2",
6868
"@types/babel__standalone": "7.1.9",
69-
"@types/d3-scale": "4.0.9",
7069
"@types/lodash-es": "4.17.12",
7170
"@types/react": "19.2.7",
7271
"@types/react-dom": "19.2.3",

libs/@hashintel/petrinaut/src/components/sub-view/horizontal/horizontal-tabs-container.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,21 @@ const tabButtonStyle = cva({
4141
},
4242
});
4343

44-
const contentStyle = css({
45-
fontSize: "xs",
46-
padding: "3",
47-
flex: "[1]",
48-
overflowY: "auto",
44+
const contentStyle = cva({
45+
base: {
46+
fontSize: "xs",
47+
flex: "[1]",
48+
overflowY: "auto",
49+
},
50+
variants: {
51+
padded: {
52+
// Includes the 4px that previously came from the outer panel container,
53+
// so padded subviews keep the same visual inset.
54+
true: { padding: "[16px]" },
55+
false: { padding: "0" },
56+
},
57+
},
58+
defaultVariants: { padded: true },
4959
});
5060

5161
interface TabButtonProps {
@@ -122,7 +132,7 @@ export const HorizontalTabsContainer: React.FC<
122132
{/* Content */}
123133
<div
124134
id={`tabpanel-${activeTabId}`}
125-
className={contentStyle}
135+
className={contentStyle({ padded: !activeSubView.noPadding })}
126136
role="tabpanel"
127137
aria-labelledby={`tab-${activeTabId}`}
128138
>
@@ -196,7 +206,7 @@ export const HorizontalTabsContent: React.FC<{
196206
return (
197207
<div
198208
id={tabpanelId}
199-
className={contentStyle}
209+
className={contentStyle({ padded: !activeSubView.noPadding })}
200210
role="tabpanel"
201211
aria-labelledby={tabId}
202212
>

libs/@hashintel/petrinaut/src/components/sub-view/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,11 @@ export interface SubView {
7272
* Only affects vertical layout. When set, the section can be resized by dragging its bottom edge.
7373
*/
7474
resizable?: SubViewResizeConfig;
75+
/**
76+
* When true, the horizontal tab content wrapper renders with no padding,
77+
* letting the subview occupy the full width/height of the panel area.
78+
* Useful for visualizations like charts that manage their own bounds.
79+
* Defaults to false.
80+
*/
81+
noPadding?: boolean;
7582
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useEffect, useRef, useState, type RefObject } from "react";
2+
3+
interface ElementSize {
4+
width: number;
5+
height: number;
6+
}
7+
8+
interface UseElementSizeOptions {
9+
/**
10+
* Debounce interval in milliseconds. When set, the returned size only
11+
* updates at most once per interval, batching rapid resize events.
12+
* Useful for expensive downstream work (e.g. chart recreation).
13+
* Defaults to 0 (no debounce — updates on every ResizeObserver callback).
14+
*/
15+
debounce?: number;
16+
}
17+
18+
/**
19+
* Returns the content-box size of a DOM element, kept in sync via ResizeObserver.
20+
*
21+
* Returns `null` until the element is mounted and the first observation fires.
22+
* Supports an optional `debounce` interval to throttle updates.
23+
*
24+
* @example
25+
* ```tsx
26+
* const ref = useRef<HTMLDivElement>(null);
27+
* const size = useElementSize(ref, { debounce: 100 });
28+
*
29+
* return <div ref={ref}>{size && `${size.width} × ${size.height}`}</div>;
30+
* ```
31+
*/
32+
export function useElementSize(
33+
ref: RefObject<HTMLElement | null>,
34+
options?: UseElementSizeOptions,
35+
): ElementSize | null {
36+
"use no memo"; // imperative observer + timer management
37+
const [size, setSize] = useState<ElementSize | null>(null);
38+
const debounceMs = options?.debounce ?? 0;
39+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
40+
41+
useEffect(() => {
42+
const el = ref.current;
43+
if (!el) {
44+
return;
45+
}
46+
47+
const update = (width: number, height: number) => {
48+
setSize((prev) => {
49+
if (prev && prev.width === width && prev.height === height) {
50+
return prev; // avoid spurious re-renders
51+
}
52+
return { width, height };
53+
});
54+
};
55+
56+
const ro = new ResizeObserver((entries) => {
57+
const entry = entries[0];
58+
if (!entry) {
59+
return;
60+
}
61+
const { width, height } = entry.contentRect;
62+
if (debounceMs > 0) {
63+
if (timerRef.current != null) {
64+
clearTimeout(timerRef.current);
65+
}
66+
timerRef.current = setTimeout(() => {
67+
update(width, height);
68+
timerRef.current = null;
69+
}, debounceMs);
70+
} else {
71+
update(width, height);
72+
}
73+
});
74+
75+
ro.observe(el);
76+
77+
return () => {
78+
ro.disconnect();
79+
if (timerRef.current != null) {
80+
clearTimeout(timerRef.current);
81+
timerRef.current = null;
82+
}
83+
};
84+
}, [ref, debounceMs]);
85+
86+
return size;
87+
}

libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const panelStyle = cva({
5252
});
5353

5454
const panelContainerStyle = css({
55-
padding: "[4px]",
5655
display: "flex",
5756
flexDirection: "column",
5857
});
@@ -61,7 +60,7 @@ const headerStyle = css({
6160
display: "flex",
6261
alignItems: "center",
6362
justifyContent: "space-between",
64-
padding: "[2px]",
63+
padding: "[6px]",
6564
flexShrink: 0,
6665
});
6766

0 commit comments

Comments
 (0)