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 &&