refactor: replace tab layout modes with per-tab proportional sizing
This commit is contained in:
@@ -13,14 +13,11 @@ type UseWorkspaceTabLayoutInput = {
|
|||||||
actionsReservedWidth: number;
|
actionsReservedWidth: number;
|
||||||
rowPaddingHorizontal: number;
|
rowPaddingHorizontal: number;
|
||||||
tabGap: number;
|
tabGap: number;
|
||||||
minTabWidth: number;
|
|
||||||
maxTabWidth: number;
|
maxTabWidth: number;
|
||||||
tabIconWidth: number;
|
tabIconWidth: number;
|
||||||
tabHorizontalPadding: number;
|
tabHorizontalPadding: number;
|
||||||
estimatedCharWidth: number;
|
estimatedCharWidth: number;
|
||||||
closeButtonWidth: number;
|
closeButtonWidth: number;
|
||||||
compactLabelCharCap?: number;
|
|
||||||
compactDenseLabelCharCap?: number;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,11 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
actionsReservedWidth: Math.max(0, tabsActionsWidth),
|
actionsReservedWidth: Math.max(0, tabsActionsWidth),
|
||||||
rowPaddingHorizontal: theme.spacing[2],
|
rowPaddingHorizontal: theme.spacing[2],
|
||||||
tabGap: theme.spacing[1],
|
tabGap: theme.spacing[1],
|
||||||
minTabWidth: 60,
|
maxTabWidth: 150,
|
||||||
maxTabWidth: 260,
|
|
||||||
tabIconWidth: 14,
|
tabIconWidth: 14,
|
||||||
tabHorizontalPadding: theme.spacing[3],
|
tabHorizontalPadding: theme.spacing[3],
|
||||||
estimatedCharWidth: 7,
|
estimatedCharWidth: 7,
|
||||||
closeButtonWidth: 22,
|
closeButtonWidth: 22,
|
||||||
compactLabelCharCap: 9,
|
|
||||||
compactDenseLabelCharCap: 7,
|
|
||||||
}),
|
}),
|
||||||
[tabsActionsWidth, theme.spacing]
|
[tabsActionsWidth, theme.spacing]
|
||||||
);
|
);
|
||||||
@@ -118,6 +115,7 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
horizontal
|
horizontal
|
||||||
|
scrollEnabled={layout.requiresHorizontalScrollFallback}
|
||||||
testID="workspace-tabs-scroll"
|
testID="workspace-tabs-scroll"
|
||||||
style={styles.tabsScroll}
|
style={styles.tabsScroll}
|
||||||
contentContainerStyle={styles.tabsContent}
|
contentContainerStyle={styles.tabsContent}
|
||||||
@@ -129,7 +127,7 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
useDragHandle
|
useDragHandle
|
||||||
disabled={tabs.length < 2}
|
disabled={tabs.length < 2}
|
||||||
onDragEnd={onReorderTabs}
|
onDragEnd={onReorderTabs}
|
||||||
renderItem={({ item: tab, dragHandleProps }) => {
|
renderItem={({ item: tab, index, dragHandleProps }) => {
|
||||||
const isActive = tab.key === activeTabKey;
|
const isActive = tab.key === activeTabKey;
|
||||||
const tabAgent = tab.kind === "agent" ? agentsById.get(tab.agentId) ?? null : null;
|
const tabAgent = tab.kind === "agent" ? agentsById.get(tab.agentId) ?? null : null;
|
||||||
const isCloseHovered = hoveredCloseTabKey === tab.key;
|
const isCloseHovered = hoveredCloseTabKey === tab.key;
|
||||||
@@ -143,6 +141,11 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
tab.kind === "terminal" && killTerminalPending && killTerminalId === tab.terminalId;
|
tab.kind === "terminal" && killTerminalPending && killTerminalId === tab.terminalId;
|
||||||
const isClosingTab = isClosingAgent || isClosingTerminal;
|
const isClosingTab = isClosingAgent || isClosingTerminal;
|
||||||
const shouldShowCloseButton = layout.closeButtonPolicy === "all";
|
const shouldShowCloseButton = layout.closeButtonPolicy === "all";
|
||||||
|
const layoutItem = layout.items[index] ?? null;
|
||||||
|
const resolvedTabWidth = layoutItem?.width ?? 150;
|
||||||
|
const showLabel = layoutItem?.showLabel ?? true;
|
||||||
|
const labelCharCap = layoutItem?.labelCharCap ?? tab.label.length;
|
||||||
|
const renderedLabel = showLabel ? tab.label.slice(0, Math.max(1, labelCharCap)) : "";
|
||||||
const iconColor = isActive ? theme.colors.foreground : theme.colors.foregroundMuted;
|
const iconColor = isActive ? theme.colors.foreground : theme.colors.foregroundMuted;
|
||||||
const tabAgentStatusBucket = tabAgent
|
const tabAgentStatusBucket = tabAgent
|
||||||
? deriveSidebarStateBucket({
|
? deriveSidebarStateBucket({
|
||||||
@@ -199,14 +202,11 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
enabledOnMobile={false}
|
enabledOnMobile={false}
|
||||||
style={({ hovered, pressed }) => [
|
style={({ hovered, pressed }) => [
|
||||||
styles.tab,
|
styles.tab,
|
||||||
layout.mode === "icon" && styles.tabIconOnly,
|
{
|
||||||
layout.mode === "icon" && {
|
minWidth: resolvedTabWidth,
|
||||||
minWidth: layout.tabMaxWidth,
|
width: resolvedTabWidth,
|
||||||
width: layout.tabMaxWidth,
|
maxWidth: resolvedTabWidth,
|
||||||
maxWidth: layout.tabMaxWidth,
|
|
||||||
},
|
},
|
||||||
layout.mode === "compact" && styles.tabCompact,
|
|
||||||
layout.mode === "compact" && { maxWidth: layout.tabMaxWidth },
|
|
||||||
isActive && styles.tabActive,
|
isActive && styles.tabActive,
|
||||||
(hovered || pressed || isCloseHovered) && styles.tabHovered,
|
(hovered || pressed || isCloseHovered) && styles.tabHovered,
|
||||||
]}
|
]}
|
||||||
@@ -235,7 +235,7 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
style={styles.tabHandle}
|
style={styles.tabHandle}
|
||||||
>
|
>
|
||||||
<View style={styles.tabIcon}>{icon}</View>
|
<View style={styles.tabIcon}>{icon}</View>
|
||||||
{layout.showLabels ? (
|
{showLabel ? (
|
||||||
tab.kind === "agent" && tab.titleState === "loading" ? (
|
tab.kind === "agent" && tab.titleState === "loading" ? (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -252,7 +252,7 @@ export function WorkspaceDesktopTabsRow({
|
|||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{renderedLabel}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
@@ -424,8 +424,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingVertical: theme.spacing[1],
|
paddingVertical: theme.spacing[1],
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
minWidth: 60,
|
|
||||||
maxWidth: 260,
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
@@ -433,15 +431,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
tabIconOnly: {
|
|
||||||
minWidth: 40,
|
|
||||||
width: 40,
|
|
||||||
maxWidth: 40,
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
tabCompact: {
|
|
||||||
paddingHorizontal: theme.spacing[2],
|
|
||||||
},
|
|
||||||
tabHandle: {
|
tabHandle: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -6,87 +6,76 @@ const metrics = {
|
|||||||
actionsReservedWidth: 120,
|
actionsReservedWidth: 120,
|
||||||
rowPaddingHorizontal: 8,
|
rowPaddingHorizontal: 8,
|
||||||
tabGap: 4,
|
tabGap: 4,
|
||||||
minTabWidth: 60,
|
maxTabWidth: 150,
|
||||||
maxTabWidth: 260,
|
|
||||||
tabIconWidth: 14,
|
tabIconWidth: 14,
|
||||||
tabHorizontalPadding: 12,
|
tabHorizontalPadding: 12,
|
||||||
estimatedCharWidth: 7,
|
estimatedCharWidth: 7,
|
||||||
closeButtonWidth: 22,
|
closeButtonWidth: 22,
|
||||||
compactLabelCharCap: 9,
|
|
||||||
compactDenseLabelCharCap: 7,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("computeWorkspaceTabLayout", () => {
|
describe("computeWorkspaceTabLayout", () => {
|
||||||
it("keeps full width tabs when space is available", () => {
|
it("caps tab width to the fixed maximum when enough space is available", () => {
|
||||||
const result = computeWorkspaceTabLayout({
|
const result = computeWorkspaceTabLayout({
|
||||||
viewportWidth: 1200,
|
viewportWidth: 1200,
|
||||||
tabLabelLengths: [8, 10, 7],
|
tabLabelLengths: [8, 10, 7],
|
||||||
metrics,
|
metrics,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.mode).toBe("full");
|
|
||||||
expect(result.showLabels).toBe(true);
|
|
||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
|
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
||||||
|
expect(result.items).toHaveLength(3);
|
||||||
|
expect(result.items.every((item) => item.showLabel)).toBe(true);
|
||||||
|
expect(Math.max(...result.items.map((item) => item.width))).toBeLessThanOrEqual(150);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses compact mode before icon-only", () => {
|
it("shrinks tab widths proportionally before hiding labels", () => {
|
||||||
const result = computeWorkspaceTabLayout({
|
const result = computeWorkspaceTabLayout({
|
||||||
viewportWidth: 570,
|
viewportWidth: 520,
|
||||||
tabLabelLengths: [24, 12, 8],
|
tabLabelLengths: [24, 12, 8],
|
||||||
metrics,
|
metrics,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.mode).toBe("compact");
|
|
||||||
expect(result.showLabels).toBe(true);
|
|
||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
expect(result.tabMaxWidth).toBeGreaterThan(108);
|
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
||||||
|
expect(result.items.map((item) => item.width)).toEqual([137, 132, 108]);
|
||||||
|
expect(result.items.every((item) => item.showLabel)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to icon mode when compact labels still cannot fit", () => {
|
it("collapses to icon-only before allowing horizontal scroll fallback", () => {
|
||||||
|
const result = computeWorkspaceTabLayout({
|
||||||
|
viewportWidth: 388,
|
||||||
|
tabLabelLengths: [14, 14, 14, 14],
|
||||||
|
metrics,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
|
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
||||||
|
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
||||||
|
expect(result.items.every((item) => item.showLabel === false)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows horizontal scroll only when icon-only tabs still cannot fit", () => {
|
||||||
const result = computeWorkspaceTabLayout({
|
const result = computeWorkspaceTabLayout({
|
||||||
viewportWidth: 300,
|
viewportWidth: 300,
|
||||||
tabLabelLengths: [14, 14, 14, 14],
|
tabLabelLengths: [14, 14, 14, 14],
|
||||||
metrics,
|
metrics,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.mode).toBe("icon");
|
|
||||||
expect(result.showLabels).toBe(false);
|
|
||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
|
expect(result.requiresHorizontalScrollFallback).toBe(true);
|
||||||
|
expect(result.items.map((item) => item.width)).toEqual([60, 60, 60, 60]);
|
||||||
|
expect(result.items.every((item) => item.showLabel === false)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps icon mode without scroll when icons can fit", () => {
|
it("returns empty layout details when there are no tabs", () => {
|
||||||
const result = computeWorkspaceTabLayout({
|
const result = computeWorkspaceTabLayout({
|
||||||
viewportWidth: 380,
|
viewportWidth: 1200,
|
||||||
tabLabelLengths: [20, 20, 20, 20],
|
tabLabelLengths: [],
|
||||||
metrics,
|
metrics,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.mode).toBe("icon");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps compact-with-label mode for realistic desktop width with nine tabs", () => {
|
|
||||||
const result = computeWorkspaceTabLayout({
|
|
||||||
viewportWidth: 1180,
|
|
||||||
tabLabelLengths: [18, 21, 14, 15, 19, 12, 17, 16, 20],
|
|
||||||
metrics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.mode).toBe("compact");
|
|
||||||
expect(result.showLabels).toBe(true);
|
|
||||||
expect(result.closeButtonPolicy).toBe("all");
|
expect(result.closeButtonPolicy).toBe("all");
|
||||||
expect(result.tabMaxWidth).toBeGreaterThan(94);
|
expect(result.requiresHorizontalScrollFallback).toBe(false);
|
||||||
});
|
expect(result.items).toEqual([]);
|
||||||
|
|
||||||
it("falls back to icon mode sooner at tighter widths when close buttons stay visible", () => {
|
|
||||||
const result = computeWorkspaceTabLayout({
|
|
||||||
viewportWidth: 1100,
|
|
||||||
tabLabelLengths: [18, 21, 14, 15, 19, 12, 17, 16, 20],
|
|
||||||
metrics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.mode).toBe("icon");
|
|
||||||
expect(result.showLabels).toBe(false);
|
|
||||||
expect(result.closeButtonPolicy).toBe("all");
|
|
||||||
expect(result.tabMaxWidth).toBe(58);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export type WorkspaceTabLayoutMode = "full" | "compact" | "icon";
|
|
||||||
export type WorkspaceTabCloseButtonPolicy = "all";
|
export type WorkspaceTabCloseButtonPolicy = "all";
|
||||||
|
|
||||||
export type WorkspaceTabLayoutInput = {
|
export type WorkspaceTabLayoutInput = {
|
||||||
@@ -9,22 +8,24 @@ export type WorkspaceTabLayoutInput = {
|
|||||||
actionsReservedWidth: number;
|
actionsReservedWidth: number;
|
||||||
rowPaddingHorizontal: number;
|
rowPaddingHorizontal: number;
|
||||||
tabGap: number;
|
tabGap: number;
|
||||||
minTabWidth: number;
|
|
||||||
maxTabWidth: number;
|
maxTabWidth: number;
|
||||||
tabIconWidth: number;
|
tabIconWidth: number;
|
||||||
tabHorizontalPadding: number;
|
tabHorizontalPadding: number;
|
||||||
estimatedCharWidth: number;
|
estimatedCharWidth: number;
|
||||||
closeButtonWidth: number;
|
closeButtonWidth: number;
|
||||||
compactLabelCharCap?: number;
|
|
||||||
compactDenseLabelCharCap?: number;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkspaceTabLayoutItem = {
|
||||||
|
width: number;
|
||||||
|
showLabel: boolean;
|
||||||
|
labelCharCap: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkspaceTabLayoutResult = {
|
export type WorkspaceTabLayoutResult = {
|
||||||
mode: WorkspaceTabLayoutMode;
|
items: WorkspaceTabLayoutItem[];
|
||||||
showLabels: boolean;
|
|
||||||
closeButtonPolicy: WorkspaceTabCloseButtonPolicy;
|
closeButtonPolicy: WorkspaceTabCloseButtonPolicy;
|
||||||
tabMaxWidth: number;
|
requiresHorizontalScrollFallback: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function clamp(value: number, min: number, max: number): number {
|
function clamp(value: number, min: number, max: number): number {
|
||||||
@@ -45,27 +46,15 @@ function sum(values: number[]): number {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeTotalRowWidth(input: { itemWidths: number[]; tabGap: number; rowPaddingHorizontal: number }): number {
|
|
||||||
if (input.itemWidths.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
input.rowPaddingHorizontal * 2 +
|
|
||||||
sum(input.itemWidths) +
|
|
||||||
Math.max(input.itemWidths.length - 1, 0) * input.tabGap
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function computeWorkspaceTabLayout(
|
export function computeWorkspaceTabLayout(
|
||||||
input: WorkspaceTabLayoutInput
|
input: WorkspaceTabLayoutInput
|
||||||
): WorkspaceTabLayoutResult {
|
): WorkspaceTabLayoutResult {
|
||||||
const tabCount = input.tabLabelLengths.length;
|
const tabCount = input.tabLabelLengths.length;
|
||||||
if (tabCount === 0) {
|
if (tabCount === 0) {
|
||||||
return {
|
return {
|
||||||
mode: "full",
|
items: [],
|
||||||
showLabels: true,
|
|
||||||
closeButtonPolicy: "all",
|
closeButtonPolicy: "all",
|
||||||
tabMaxWidth: input.metrics.maxTabWidth,
|
requiresHorizontalScrollFallback: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,60 +62,52 @@ export function computeWorkspaceTabLayout(
|
|||||||
0,
|
0,
|
||||||
input.viewportWidth - input.metrics.rowHorizontalInset * 2 - input.metrics.actionsReservedWidth
|
input.viewportWidth - input.metrics.rowHorizontalInset * 2 - input.metrics.actionsReservedWidth
|
||||||
);
|
);
|
||||||
const baseTabWidth = input.metrics.tabIconWidth + input.metrics.tabHorizontalPadding * 2;
|
const rowOverhead =
|
||||||
|
input.metrics.rowPaddingHorizontal * 2 + Math.max(tabCount - 1, 0) * input.metrics.tabGap;
|
||||||
|
const availableTabsWidth = Math.max(0, availableWidth - rowOverhead);
|
||||||
|
const iconOnlyTabWidth =
|
||||||
|
input.metrics.tabIconWidth + input.metrics.tabHorizontalPadding * 2 + input.metrics.closeButtonWidth;
|
||||||
const estimateLabelWidth = (labelLength: number) => labelLength * input.metrics.estimatedCharWidth;
|
const estimateLabelWidth = (labelLength: number) => labelLength * input.metrics.estimatedCharWidth;
|
||||||
const compactLabelCharCap = Math.max(1, input.metrics.compactLabelCharCap ?? 12);
|
const desiredTabWidths = input.tabLabelLengths.map((rawLength) => {
|
||||||
const compactDenseLabelCharCap = Math.max(
|
const labelLength = Math.max(rawLength, 1);
|
||||||
1,
|
const estimatedWidth = iconOnlyTabWidth + estimateLabelWidth(labelLength);
|
||||||
input.metrics.compactDenseLabelCharCap ?? Math.max(1, compactLabelCharCap - 2)
|
return clamp(estimatedWidth, iconOnlyTabWidth, input.metrics.maxTabWidth);
|
||||||
|
});
|
||||||
|
|
||||||
|
const desiredTotalTabsWidth = sum(desiredTabWidths);
|
||||||
|
const iconOnlyTotalTabsWidth = iconOnlyTabWidth * tabCount;
|
||||||
|
const requiresHorizontalScrollFallback = availableTabsWidth < iconOnlyTotalTabsWidth;
|
||||||
|
|
||||||
|
let resolvedWidths: number[];
|
||||||
|
if (desiredTotalTabsWidth <= availableTabsWidth) {
|
||||||
|
resolvedWidths = desiredTabWidths;
|
||||||
|
} else if (availableTabsWidth <= iconOnlyTotalTabsWidth) {
|
||||||
|
resolvedWidths = new Array(tabCount).fill(iconOnlyTabWidth);
|
||||||
|
} else {
|
||||||
|
const desiredExpandableWidth = desiredTotalTabsWidth - iconOnlyTotalTabsWidth;
|
||||||
|
const availableExpandableWidth = availableTabsWidth - iconOnlyTotalTabsWidth;
|
||||||
|
const proportionalRatio =
|
||||||
|
desiredExpandableWidth > 0 ? availableExpandableWidth / desiredExpandableWidth : 0;
|
||||||
|
resolvedWidths = desiredTabWidths.map(
|
||||||
|
(desiredWidth) => iconOnlyTabWidth + (desiredWidth - iconOnlyTabWidth) * proportionalRatio
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const roundedWidths = resolvedWidths.map((width) =>
|
||||||
|
Math.round(clamp(width, iconOnlyTabWidth, input.metrics.maxTabWidth))
|
||||||
);
|
);
|
||||||
const effectiveCompactLabelCharCap = tabCount >= 8 ? compactDenseLabelCharCap : compactLabelCharCap;
|
|
||||||
|
|
||||||
const fullTabWidths = input.tabLabelLengths.map((rawLength) => {
|
|
||||||
const labelLength = Math.max(rawLength, 1);
|
|
||||||
const estimatedWidth = baseTabWidth + estimateLabelWidth(labelLength) + input.metrics.closeButtonWidth;
|
|
||||||
return clamp(estimatedWidth, input.metrics.minTabWidth, input.metrics.maxTabWidth);
|
|
||||||
});
|
|
||||||
const fullTotal = computeTotalRowWidth({
|
|
||||||
itemWidths: fullTabWidths,
|
|
||||||
tabGap: input.metrics.tabGap,
|
|
||||||
rowPaddingHorizontal: input.metrics.rowPaddingHorizontal,
|
|
||||||
});
|
|
||||||
if (fullTotal <= availableWidth) {
|
|
||||||
return {
|
|
||||||
mode: "full",
|
|
||||||
showLabels: true,
|
|
||||||
closeButtonPolicy: "all",
|
|
||||||
tabMaxWidth: input.metrics.maxTabWidth,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const compactTabWidths = input.tabLabelLengths.map((rawLength) => {
|
|
||||||
const labelLength = Math.max(rawLength, 1);
|
|
||||||
const estimatedWidth =
|
|
||||||
baseTabWidth +
|
|
||||||
estimateLabelWidth(Math.min(labelLength, effectiveCompactLabelCharCap)) +
|
|
||||||
input.metrics.closeButtonWidth;
|
|
||||||
return clamp(estimatedWidth, input.metrics.minTabWidth, input.metrics.maxTabWidth);
|
|
||||||
});
|
|
||||||
const compactTotal = computeTotalRowWidth({
|
|
||||||
itemWidths: compactTabWidths,
|
|
||||||
tabGap: input.metrics.tabGap,
|
|
||||||
rowPaddingHorizontal: input.metrics.rowPaddingHorizontal,
|
|
||||||
});
|
|
||||||
if (compactTotal <= availableWidth) {
|
|
||||||
return {
|
|
||||||
mode: "compact",
|
|
||||||
showLabels: true,
|
|
||||||
closeButtonPolicy: "all",
|
|
||||||
tabMaxWidth: Math.max(...compactTabWidths),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mode: "icon",
|
items: roundedWidths.map((width) => {
|
||||||
showLabels: false,
|
const rawCharCap = Math.floor((width - iconOnlyTabWidth) / input.metrics.estimatedCharWidth);
|
||||||
|
const labelCharCap = Math.max(0, rawCharCap);
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
showLabel: labelCharCap > 0,
|
||||||
|
labelCharCap,
|
||||||
|
};
|
||||||
|
}),
|
||||||
closeButtonPolicy: "all",
|
closeButtonPolicy: "all",
|
||||||
tabMaxWidth: 58,
|
requiresHorizontalScrollFallback,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user