From 8723580071e577f3a58f3a9d88521ea5461deb8d Mon Sep 17 00:00:00 2001 From: Tonio Date: Mon, 19 Oct 2026 19:26:23 -0700 Subject: [PATCH] fix(ui): refine task side panel tabs (#14949) --- ui/src/components/side-panel/README.md | 2 +- ui/src/components/side-panel/SidePanelTab.tsx | 38 +++-- .../side-panel/SidePanelTabs.test.tsx | 50 +++--- .../components/side-panel/SidePanelTabs.tsx | 151 ++++++++++++++---- .../task-side-panel/TaskSidePanel.test.tsx | 4 +- ui/src/index.css | 36 ++++- .../task-side-panel-tabs.stories.tsx | 74 +++++++++ 7 files changed, 288 insertions(+), 67 deletions(-) create mode 100644 ui/storybook/stories/navigation-preferences/task-side-panel-tabs.stories.tsx diff --git a/ui/src/components/side-panel/README.md b/ui/src/components/side-panel/README.md index 8fca6ee2381..9bd178f23db 100644 --- a/ui/src/components/side-panel/README.md +++ b/ui/src/components/side-panel/README.md @@ -12,7 +12,7 @@ The components exported by `@/components/side-panel` are controlled, domain-free Never put React nodes or fetched content in a persisted `SidePanelTabRecord`. Project icons and live status into `SidePanelTabItem` at render time. -`SidePanelTabs` also accepts `appearance="streamlined-task"` for the experimental task-detail shell. That appearance preserves the approved pre-rebase Codex-inspired treatment—equal-width text tabs, separated surfaces, edge fades, and hover-revealed close actions—while the default appearance remains master's portable side-panel chrome. +`SidePanelTabs` also accepts `appearance="streamlined-task"` for the experimental task-detail shell. Its left-aligned text tabs share one width: 100px when every label fits, or 125px when any label needs more room. They shrink equally as the panel narrows, then scroll once the usable width floor is reached. Clipped labels fade. The tabs keep a subtle active fill and omit dividers. The active tab shows its round close action; inactive tabs reveal theirs on hover. Every close action has its own hover highlight. The default appearance remains the portable side-panel chrome. Use `headerSize="task-detail"` on `SidePanelFrame` when the panel shares the 60px task breadcrumb row. It keeps the task-detail footer treatment while matching the default portable header height. diff --git a/ui/src/components/side-panel/SidePanelTab.tsx b/ui/src/components/side-panel/SidePanelTab.tsx index af419667c5a..c52348e6b0f 100644 --- a/ui/src/components/side-panel/SidePanelTab.tsx +++ b/ui/src/components/side-panel/SidePanelTab.tsx @@ -12,6 +12,7 @@ export interface SidePanelTabProps { active: boolean; closable?: boolean; disabled?: boolean; + suppressTooltip?: boolean; tabRef?: Ref; dragHandleProps?: ButtonHTMLAttributes; onSelect: () => void; @@ -31,6 +32,7 @@ export function SidePanelTab({ active, closable = true, disabled = false, + suppressTooltip = false, tabRef, dragHandleProps, onSelect, @@ -44,6 +46,7 @@ export function SidePanelTab({ const wrapperRef = useRef(null); const labelRef = useRef(null); const [labelIsTruncated, setLabelIsTruncated] = useState(false); + const [tooltipOpen, setTooltipOpen] = useState(false); const sizingKey = `${appearance}:${label}:${icon ? "icon" : "no-icon"}:${status ? "status" : "no-status"}:${closable ? "closable" : "fixed"}`; const [stableWidth, setStableWidth] = useState<{ key: string; width: number } | null>(null); const hasStableWidth = stableWidth?.key === sizingKey; @@ -69,6 +72,10 @@ export function SidePanelTab({ return () => observer.disconnect(); }, [label]); + useEffect(() => { + if (suppressTooltip || !labelIsTruncated) setTooltipOpen(false); + }, [labelIsTruncated, suppressTooltip]); + return (
- + setTooltipOpen(open && labelIsTruncated && !suppressTooltip)} + >