Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions libs/@hashintel/petrinaut/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
"@hashintel/refractive": "workspace:^",
"@monaco-editor/react": "4.8.0-rc.3",
"@xyflow/react": "12.10.1",
"d3-scale": "4.0.2",
"elkjs": "0.11.0",
"fuzzysort": "3.1.0",
"lodash-es": "4.18.1",
"monaco-editor": "0.55.1",
"react-icons": "5.5.0",
"react-resizable-panels": "4.6.5",
"typescript": "5.9.3",
"uplot": "1.6.32",
Comment thread
kube marked this conversation as resolved.
"uuid": "13.0.0",
"vscode-languageserver-types": "3.17.5",
"web-worker": "1.4.1",
Expand All @@ -66,7 +66,6 @@
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
"@types/babel__standalone": "7.1.9",
"@types/d3-scale": "4.0.9",
"@types/lodash-es": "4.17.12",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,21 @@ const tabButtonStyle = cva({
},
});

const contentStyle = css({
fontSize: "xs",
padding: "3",
flex: "[1]",
overflowY: "auto",
const contentStyle = cva({
base: {
fontSize: "xs",
flex: "[1]",
overflowY: "auto",
},
variants: {
padded: {
// Includes the 4px that previously came from the outer panel container,
// so padded subviews keep the same visual inset.
true: { padding: "[16px]" },
false: { padding: "0" },
},
},
defaultVariants: { padded: true },
});

interface TabButtonProps {
Expand Down Expand Up @@ -122,7 +132,7 @@ export const HorizontalTabsContainer: React.FC<
{/* Content */}
<div
id={`tabpanel-${activeTabId}`}
className={contentStyle}
className={contentStyle({ padded: !activeSubView.noPadding })}
role="tabpanel"
aria-labelledby={`tab-${activeTabId}`}
>
Expand Down Expand Up @@ -196,7 +206,7 @@ export const HorizontalTabsContent: React.FC<{
return (
<div
id={tabpanelId}
className={contentStyle}
className={contentStyle({ padded: !activeSubView.noPadding })}
role="tabpanel"
aria-labelledby={tabId}
>
Expand Down
7 changes: 7 additions & 0 deletions libs/@hashintel/petrinaut/src/components/sub-view/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,11 @@ export interface SubView {
* Only affects vertical layout. When set, the section can be resized by dragging its bottom edge.
*/
resizable?: SubViewResizeConfig;
/**
* When true, the horizontal tab content wrapper renders with no padding,
* letting the subview occupy the full width/height of the panel area.
* Useful for visualizations like charts that manage their own bounds.
* Defaults to false.
*/
noPadding?: boolean;
}
87 changes: 87 additions & 0 deletions libs/@hashintel/petrinaut/src/hooks/use-element-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useEffect, useRef, useState, type RefObject } from "react";

interface ElementSize {
width: number;
height: number;
}

interface UseElementSizeOptions {
/**
* Debounce interval in milliseconds. When set, the returned size only
* updates at most once per interval, batching rapid resize events.
* Useful for expensive downstream work (e.g. chart recreation).
* Defaults to 0 (no debounce β€” updates on every ResizeObserver callback).
*/
debounce?: number;
}

/**
* Returns the content-box size of a DOM element, kept in sync via ResizeObserver.
*
* Returns `null` until the element is mounted and the first observation fires.
* Supports an optional `debounce` interval to throttle updates.
*
* @example
* ```tsx
* const ref = useRef<HTMLDivElement>(null);
* const size = useElementSize(ref, { debounce: 100 });
*
* return <div ref={ref}>{size && `${size.width} Γ— ${size.height}`}</div>;
* ```
*/
export function useElementSize(
Comment thread
kube marked this conversation as resolved.
ref: RefObject<HTMLElement | null>,
options?: UseElementSizeOptions,
): ElementSize | null {
"use no memo"; // imperative observer + timer management
const [size, setSize] = useState<ElementSize | null>(null);
const debounceMs = options?.debounce ?? 0;
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(() => {
const el = ref.current;
if (!el) {
return;
}

const update = (width: number, height: number) => {
setSize((prev) => {
Comment thread
kube marked this conversation as resolved.
if (prev && prev.width === width && prev.height === height) {
return prev; // avoid spurious re-renders
}
return { width, height };
});
};

const ro = new ResizeObserver((entries) => {
const entry = entries[0];
if (!entry) {
return;
}
const { width, height } = entry.contentRect;
if (debounceMs > 0) {
if (timerRef.current != null) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
update(width, height);
timerRef.current = null;
}, debounceMs);
} else {
update(width, height);
}
});

ro.observe(el);

return () => {
ro.disconnect();
if (timerRef.current != null) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
};
}, [ref, debounceMs]);

return size;
}
Comment thread
cursor[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const panelStyle = cva({
});

const panelContainerStyle = css({
padding: "[4px]",
display: "flex",
flexDirection: "column",
});
Expand All @@ -61,7 +60,7 @@ const headerStyle = css({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "[2px]",
padding: "[6px]",
flexShrink: 0,
});

Expand Down
Loading
Loading