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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ioai/rosview",
"version": "1.6.3",
"version": "1.7.0",
"description": "High-performance robotics data visualization for MCAP, ROS bag, ROS2 db3, HDF5 and BVH — embeddable React component and standalone SPA",
"keywords": [
"ros",
Expand Down
5 changes: 3 additions & 2 deletions src/features/panels/Image/ImagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { isH264MessageEvent, toWorkerFrame } from './core/messageFrameAdapter';
import { applyDepthTopicPreset } from './core/depthColorDefaults';
import type { ImageConfig } from './defaults';
import { TopicQuickPicker } from '../framework/TopicQuickPicker';
import { PanelTopicBar } from '../framework/PanelTopicBar';
import ImageRenderWorkerClass from './core/ImageRender.worker.ts?worker&inline';

type ColorOptions = Pick<ImageConfig, 'colorMode' | 'flatColor' | 'gradient' | 'colorMap' | 'explicitAlpha' | 'minValue' | 'maxValue'>;
Expand Down Expand Up @@ -304,7 +305,7 @@ export const ImagePanel: React.FC<ImagePanelProps> = (props) => {
style={{ background: backgroundColor }}
data-testid="image-panel"
>
<div className="flex shrink-0 items-center gap-2 border-b border-zinc-800 bg-zinc-950">
<PanelTopicBar className="border-zinc-800 bg-zinc-950">
<TopicQuickPicker
value={topic}
onChange={(nextTopic) => setConfig((prev) => applyDepthTopicPreset(nextTopic, prev))}
Expand All @@ -313,7 +314,7 @@ export const ImagePanel: React.FC<ImagePanelProps> = (props) => {
className="min-w-0 flex-1"
triggerClassName="border-zinc-700 bg-zinc-950 text-zinc-100 hover:bg-zinc-900 hover:text-zinc-50"
/>
</div>
</PanelTopicBar>
<div
ref={viewportRef}
className="flex-1 relative min-h-0 min-w-0 flex items-center justify-center"
Expand Down
5 changes: 3 additions & 2 deletions src/features/panels/RawMessages/RawMessagesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isRosImageSchema } from '@/shared/ros/rosMessageTypes';
import { formatLocalTimestamp } from '@/shared/utils/time';
import { scheduleFrame } from '@/shared/utils/rafScheduler';
import { TopicQuickPicker } from '../framework/TopicQuickPicker';
import { PanelTopicBar } from '../framework/PanelTopicBar';
import type { RawMessagesConfig } from './defaults';
import { buildRowsForMessageEvent, type FlatRow } from './shapeTree';

Expand Down Expand Up @@ -705,15 +706,15 @@ export const RawMessagesPanel: React.FC<RawMessagesPanelProps> = ({

return (
<div className="flex h-full flex-col overflow-hidden bg-background">
<div className="shrink-0 border-b bg-muted px-2 py-1">
<PanelTopicBar>
<TopicQuickPicker
value={topic}
topics={topics}
onChange={(nextTopic) => setConfig((prev) => ({ ...prev, topic: nextTopic }))}
placeholder={formatMessage({ id: 'panels.framework.topicPicker.placeholder' })}
className="min-w-0 w-full"
/>
</div>
</PanelTopicBar>

<div
ref={viewportRef}
Expand Down
67 changes: 67 additions & 0 deletions src/features/panels/framework/PanelTopicBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @vitest-environment happy-dom
*/
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { PanelTopicBar } from './PanelTopicBar';

(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

describe('PanelTopicBar', () => {
let container: HTMLDivElement;
let root: Root;

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});

afterEach(() => {
act(() => root.unmount());
container.remove();
});

function getBar(): HTMLDivElement {
const el = container.firstElementChild;
if (!(el instanceof HTMLDivElement)) throw new Error('bar div not found');
return el;
}

it('renders children inside a compact, horizontally-padded row', () => {
act(() => {
root.render(
<PanelTopicBar>
<button type="button">picker</button>
</PanelTopicBar>,
);
});
const bar = getBar();
expect(bar.textContent).toBe('picker');
// No vertical padding: height should come purely from content, matching
// the Image panel's compact reference experience (RawMessages used to
// add `py-1`, which made its bar taller than Image's).
expect(bar.className).not.toMatch(/(^|\s)py-\d/);
expect(bar.className).toContain('items-center');
expect(bar.className).toContain('px-2');
});

it('merges className overrides without losing the shared layout classes', () => {
act(() => {
root.render(
<PanelTopicBar className="border-zinc-800 bg-zinc-950">
<span>content</span>
</PanelTopicBar>,
);
});
const bar = getBar();
expect(bar.className).toContain('border-zinc-800');
expect(bar.className).toContain('bg-zinc-950');
// The override replaces the default border/background color utilities
// rather than doubling up on conflicting classes.
expect(bar.className).not.toContain('border-border');
expect(bar.className).not.toContain('bg-muted');
expect(bar.className).toContain('items-center');
});
});
25 changes: 25 additions & 0 deletions src/features/panels/framework/PanelTopicBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { cn } from '@/shared/lib/utils';

export interface PanelTopicBarProps {
className?: string;
children: React.ReactNode;
}

/**
* Shared container for a panel's top "topic picker" row (`TopicQuickPicker`
* plus any adjacent controls). Height comes purely from the row's content
* (the picker's own `h-8` trigger) with horizontal-only padding, so every
* panel using this gets the same compact height instead of each hand-rolling
* its own wrapper with inconsistent vertical padding. Colors/borders stay
* overridable via `className` (e.g. Image panel's permanently-dark chrome)
* since panels can differ there while sharing the same box model.
*/
export const PanelTopicBar: React.FC<PanelTopicBarProps> = ({ className, children }) => (
<div
data-testid="panel-topic-bar"
className={cn('flex shrink-0 items-center gap-2 border-b border-border bg-muted px-2', className)}
>
{children}
</div>
);
2 changes: 2 additions & 0 deletions src/features/panels/framework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type {
} from './foxgloveAdapter';
export { TopicQuickPicker } from './TopicQuickPicker';
export type { TopicQuickPickerProps } from './TopicQuickPicker';
export { PanelTopicBar } from './PanelTopicBar';
export type { PanelTopicBarProps } from './PanelTopicBar';
export { PanelErrorBoundary } from './PanelErrorBoundary';
export { PanelRuntimeShell } from './PanelRuntimeShell';
export {
Expand Down
Loading