diff --git a/packages/trace-viewer/src/ui/networkFilters.css b/packages/trace-viewer/src/ui/networkFilters.css index 836c7102c9bc4..1cb743c267c48 100644 --- a/packages/trace-viewer/src/ui/networkFilters.css +++ b/packages/trace-viewer/src/ui/networkFilters.css @@ -16,7 +16,6 @@ .network-filters { display: flex; - gap: 16px; background-color: var(--vscode-sideBar-background); padding: 4px 8px; min-height: 32px; @@ -24,12 +23,13 @@ .network-filters input[type="search"] { padding: 0 5px; + margin-right: 8px; } .network-filters-resource-types { display: flex; - gap: 8px; align-items: center; + width: 100% } .network-filters-resource-type { @@ -41,6 +41,10 @@ text-overflow: ellipsis; } +.network-filters-resource-types .tabbed-pane-tab { + min-width: 50px; +} + .network-filters-resource-type.selected { background-color: var(--vscode-list-inactiveSelectionBackground); } diff --git a/packages/trace-viewer/src/ui/networkFilters.tsx b/packages/trace-viewer/src/ui/networkFilters.tsx index a88332e7c65f0..4eab45c9f4817 100644 --- a/packages/trace-viewer/src/ui/networkFilters.tsx +++ b/packages/trace-viewer/src/ui/networkFilters.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { TabbedPane } from '@web/components/tabbedPane'; import './networkFilters.css'; const resourceTypes = ['All', 'Fetch', 'HTML', 'JS', 'CSS', 'Font', 'Image'] as const; @@ -41,16 +42,17 @@ export const NetworkFilters = ({ filterState, onFilterStateChange }: { />
- {resourceTypes.map(resourceType => ( -
onFilterStateChange({ ...filterState, resourceType })} - className={`network-filters-resource-type ${filterState.resourceType === resourceType ? 'selected' : ''}`} - > - {resourceType} -
- ))} + ({ + id: resourceType, + title: resourceType, + render: () => <> + }))} + selectedTab={filterState.resourceType} + setSelectedTab={tabId => onFilterStateChange({ ...filterState, resourceType: tabId as ResourceType })} + mode='default' + overflowMode='select' + />
); diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.css b/packages/trace-viewer/src/ui/networkResourceDetails.css index b3f7a5c04f01f..159d9dd1b10ad 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.css +++ b/packages/trace-viewer/src/ui/networkResourceDetails.css @@ -68,7 +68,6 @@ .tab-network .toolbar { min-height: 30px !important; background-color: initial !important; - border-bottom: 1px solid var(--vscode-panel-border); } .tab-network .toolbar::after { @@ -77,6 +76,7 @@ .tab-network .tabbed-pane-tab.selected { font-weight: bold; + background-color: var(--vscode-list-inactiveSelectionBackground) !important; } .copy-request-dropdown { diff --git a/packages/web/src/components/tabbedPane.tsx b/packages/web/src/components/tabbedPane.tsx index 95b0fa540ce42..7a7f117118bd1 100644 --- a/packages/web/src/components/tabbedPane.tsx +++ b/packages/web/src/components/tabbedPane.tsx @@ -36,37 +36,164 @@ export const TabbedPane: React.FunctionComponent<{ setSelectedTab?: (tab: string) => void, dataTestId?: string, mode?: 'default' | 'select', -}> = ({ tabs, selectedTab, setSelectedTab, leftToolbar, rightToolbar, dataTestId, mode }) => { + overflowMode?: 'none' | 'select' +}> = ({ tabs, selectedTab, setSelectedTab, leftToolbar, rightToolbar, dataTestId, mode, overflowMode }) => { const id = React.useId(); if (!selectedTab) selectedTab = tabs[0].id; if (!mode) mode = 'default'; - return
+ if (!overflowMode) + overflowMode = 'none'; + + const containerRef = React.useRef(null); + const [visibleTabs, setVisibleTabs] = React.useState(mode !== 'select' ? tabs : []); + const [overflowTabs, setOverflowTabs] = React.useState(mode === 'select' ? tabs : []); + const [tabWidths, setTabWidths] = React.useState>({}); + const [, setContainerWidth] = React.useState(0); + const [tabbedPaneWidth, setTabbedPaneWidth] = React.useState(0); + + // Initial measurements + const measureContainerTabs = React.useCallback(() => { + const container = containerRef.current; + if (!container) + return; + + const containerWidth = container.getBoundingClientRect().width; + setContainerWidth(containerWidth); + + const tabWidths: Record = {}; + const tabbedPaneTabs = container.querySelectorAll('.tabbed-pane-tab'); + tabbedPaneTabs.forEach(tabbedPane => { + const element = tabbedPane as HTMLElement; + if (element && element.title) + tabWidths[element.title] = tabbedPane.scrollWidth; + }); + setTabWidths(tabWidths); + + // For width calculation: Assume the dropdown width is 1.5x of the rightmost menu item + const tabWidthValues = Object.values(tabWidths); + const tabbedPaneWidth = 1.5 * tabWidthValues[tabWidthValues.length - 1] || 0; + if (tabbedPaneWidth > 0) + setTabbedPaneWidth(tabbedPaneWidth); + }, []); + + const calculateVisibleTabCount = React.useCallback((availableWidth: number, tabWidths: Record, tabs: TabbedPaneTabModel[]): number => { + let requiredWidth = 0; + + for (const tabWidth of Object.values(tabWidths)) { + requiredWidth += tabWidth; + if (requiredWidth > availableWidth) { + // Overflow detected, calculate how many tabs can fit + let visibleCount = 0; + let cumulativeWidth = 0; + + for (let index = 0; index < tabs.length; index++) { + const tab = tabs[index]; + const tabWidth = tabWidths[tab.title]; + cumulativeWidth += tabWidth; + + if (cumulativeWidth > availableWidth) { + visibleCount = index; + break; + } + } + return visibleCount; + } + } + + return tabs.length; + }, []); + + const adjustElementRenderings = React.useCallback(() => { + const container = containerRef.current; + if (!container) + return; + + const containerWidth = container.getBoundingClientRect().width; + setContainerWidth(containerWidth); + + const initialAvailableWidth = containerWidth - (overflowTabs.length > 0 ? tabbedPaneWidth : 0); + const finalAvailableWidth = containerWidth - tabbedPaneWidth; + + const visibleCount = calculateVisibleTabCount(initialAvailableWidth, tabWidths, tabs) === tabs.length + ? tabs.length + : calculateVisibleTabCount(finalAvailableWidth, tabWidths, tabs); + + const visibleTabsList = tabs.slice(0, visibleCount); + const overflowTabsList = tabs.slice(visibleCount); + + setVisibleTabs(visibleTabsList); + setOverflowTabs(overflowTabsList); + }, [tabWidths, overflowTabs, tabbedPaneWidth, tabs, calculateVisibleTabCount]); + + // Initial measurement and setup + React.useEffect(() => { + if (overflowMode !== 'select') + return; + + measureContainerTabs(); + }, [measureContainerTabs, overflowMode]); + + // Adjust when Tab widths change + React.useEffect(() => { + if (overflowMode !== 'select') + return; + + if (overflowTabs.length > 0) + adjustElementRenderings(); + }, [adjustElementRenderings, overflowMode, overflowTabs.length]); + + React.useEffect(() => { + if (overflowMode !== 'select') + return; + + const container = containerRef.current; + if (!container) + return; + + const handleResize = () => { + setTimeout(adjustElementRenderings, 0); + }; + + const resizeObserver = new ResizeObserver(handleResize); + resizeObserver.observe(container); + handleResize(); + + return () => { + resizeObserver.disconnect(); + }; + }, [adjustElementRenderings, overflowMode]); + + return
{ leftToolbar &&
{...leftToolbar}
} - {mode === 'default' &&
- {[...tabs.map(tab => ( - )), - ]} + {visibleTabs.length > 0 &&
+ {visibleTabs.map(visibleTab => { + const tab = tabs.find(t => t.id === visibleTab.id) || visibleTab; + return ( + + ); + })}
} - {mode === 'select' &&
+ {overflowTabs.length > 0 &&