From b33f534204a72796643cd12cbd92f16fe3bf0dc5 Mon Sep 17 00:00:00 2001 From: puxiaokang <1726541+joaner@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:53:32 +0800 Subject: [PATCH 1/2] feat(panels): share a common topic-bar wrapper between Image and RawMessages Both panels already reused the same TopicQuickPicker dropdown, but each hand-rolled its own header container with different padding: Image's had none (compact, ~33px), RawMessages' added `py-1` (~41px). Extract a shared PanelTopicBar (framework/PanelTopicBar.tsx) that standardizes the box model (flex items-center, horizontal-only padding, height driven by the picker's own h-8) while still letting each panel override colors/borders via className, and use it in both panels. RawMessages now matches Image panel's shorter topic-bar height, which is the reference experience going forward. Image panel's own styling (permanently-dark chrome) is unchanged. Plot/Audio/JointStatePlot keep their own bespoke header rows for now (different layout needs), but can adopt PanelTopicBar later with no API changes required. --- src/features/panels/Image/ImagePanel.tsx | 5 +- .../panels/RawMessages/RawMessagesPanel.tsx | 5 +- .../panels/framework/PanelTopicBar.test.tsx | 67 +++++++++++++++++++ .../panels/framework/PanelTopicBar.tsx | 25 +++++++ src/features/panels/framework/index.ts | 2 + 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/features/panels/framework/PanelTopicBar.test.tsx create mode 100644 src/features/panels/framework/PanelTopicBar.tsx diff --git a/src/features/panels/Image/ImagePanel.tsx b/src/features/panels/Image/ImagePanel.tsx index 2af9d4e..bd3811f 100644 --- a/src/features/panels/Image/ImagePanel.tsx +++ b/src/features/panels/Image/ImagePanel.tsx @@ -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; @@ -304,7 +305,7 @@ export const ImagePanel: React.FC = (props) => { style={{ background: backgroundColor }} data-testid="image-panel" > -
+ setConfig((prev) => applyDepthTopicPreset(nextTopic, prev))} @@ -313,7 +314,7 @@ export const ImagePanel: React.FC = (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" /> -
+
= ({ return (
-
+ = ({ placeholder={formatMessage({ id: 'panels.framework.topicPicker.placeholder' })} className="min-w-0 w-full" /> -
+
{ + 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( + + + , + ); + }); + 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( + + content + , + ); + }); + 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'); + }); +}); diff --git a/src/features/panels/framework/PanelTopicBar.tsx b/src/features/panels/framework/PanelTopicBar.tsx new file mode 100644 index 0000000..74f021c --- /dev/null +++ b/src/features/panels/framework/PanelTopicBar.tsx @@ -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 = ({ className, children }) => ( +
+ {children} +
+); diff --git a/src/features/panels/framework/index.ts b/src/features/panels/framework/index.ts index 893183b..703b67a 100644 --- a/src/features/panels/framework/index.ts +++ b/src/features/panels/framework/index.ts @@ -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 { From b8f4fa546880975128b50d7115d4c872616371bb Mon Sep 17 00:00:00 2001 From: puxiaokang <1726541+joaner@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:53:40 +0800 Subject: [PATCH 2/2] chore: bump version to 1.7.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6b2a10..6029301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ioai/rosview", - "version": "1.6.3", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ioai/rosview", - "version": "1.6.3", + "version": "1.7.0", "license": "MIT", "devDependencies": { "@eslint/js": "^9.39.4", diff --git a/package.json b/package.json index b9aed50..fa75263 100644 --- a/package.json +++ b/package.json @@ -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",