fix(app): lay out the tab modified dot inline
The dot was absolutely positioned at the chip's right edge, so it painted on top of a truncated label. Render it as the last flex child of the tab content row and widen a modified tab by the dot plus its gap so the label keeps the room it had.
This commit is contained in:
@@ -122,6 +122,7 @@ const TAB_ROW_PADDING_HORIZONTAL = 4;
|
|||||||
const TAB_ICON_WIDTH = 14;
|
const TAB_ICON_WIDTH = 14;
|
||||||
const TAB_CONTENT_GAP = 4;
|
const TAB_CONTENT_GAP = 4;
|
||||||
const TAB_DROP_INDICATOR_WIDTH = 4;
|
const TAB_DROP_INDICATOR_WIDTH = 4;
|
||||||
|
const TAB_MODIFIED_DOT_SIZE = 8;
|
||||||
const TAB_MIN_WIDTH = 96;
|
const TAB_MIN_WIDTH = 96;
|
||||||
const TAB_MAX_WIDTH = 160;
|
const TAB_MAX_WIDTH = 160;
|
||||||
const TAB_CLOSE_BUTTON_RESERVED_WIDTH = 0;
|
const TAB_CLOSE_BUTTON_RESERVED_WIDTH = 0;
|
||||||
@@ -607,6 +608,7 @@ interface ResolvedWorkspaceDesktopTabRowItem extends WorkspaceDesktopTabRowItem
|
|||||||
interface WorkspaceTabLabel {
|
interface WorkspaceTabLabel {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
modified: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkspaceTabLabelMeasurement {
|
interface WorkspaceTabLabelMeasurement {
|
||||||
@@ -630,12 +632,15 @@ function completeWorkspaceTabLabelWidths(
|
|||||||
measurements: Map<string, WorkspaceTabLabelMeasurement>,
|
measurements: Map<string, WorkspaceTabLabelMeasurement>,
|
||||||
): number[] | null {
|
): number[] | null {
|
||||||
const widths: number[] = [];
|
const widths: number[] = [];
|
||||||
for (const { key, label } of labels) {
|
for (const { key, label, modified } of labels) {
|
||||||
const measurement = measurements.get(key);
|
const measurement = measurements.get(key);
|
||||||
if (!measurement || measurement.label !== label || measurement.width <= 0) {
|
if (!measurement || measurement.label !== label || measurement.width <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE);
|
// The modified dot sits in the content row, so a modified tab needs that much more width
|
||||||
|
// before its label starts truncating.
|
||||||
|
const modifiedAllowance = modified ? TAB_CONTENT_GAP + TAB_MODIFIED_DOT_SIZE : 0;
|
||||||
|
widths.push(measurement.width + TAB_LABEL_LAYOUT_ALLOWANCE + modifiedAllowance);
|
||||||
}
|
}
|
||||||
return widths;
|
return widths;
|
||||||
}
|
}
|
||||||
@@ -803,6 +808,7 @@ function TabHandleContent({
|
|||||||
backdrop,
|
backdrop,
|
||||||
tabLabelSkeletonStyle,
|
tabLabelSkeletonStyle,
|
||||||
tabLabelStyle,
|
tabLabelStyle,
|
||||||
|
modifiedTestId,
|
||||||
}: {
|
}: {
|
||||||
presentation: WorkspaceTabPresentation;
|
presentation: WorkspaceTabPresentation;
|
||||||
isHighlighted: boolean;
|
isHighlighted: boolean;
|
||||||
@@ -810,7 +816,9 @@ function TabHandleContent({
|
|||||||
backdrop: SurfaceBackdrop;
|
backdrop: SurfaceBackdrop;
|
||||||
tabLabelSkeletonStyle: React.ComponentProps<typeof View>["style"];
|
tabLabelSkeletonStyle: React.ComponentProps<typeof View>["style"];
|
||||||
tabLabelStyle: React.ComponentProps<typeof Text>["style"];
|
tabLabelStyle: React.ComponentProps<typeof Text>["style"];
|
||||||
|
modifiedTestId: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const tabHandleDataSet = useMemo(
|
const tabHandleDataSet = useMemo(
|
||||||
() => ({ statusBucket: presentation.statusBucket ?? "none" }),
|
() => ({ statusBucket: presentation.statusBucket ?? "none" }),
|
||||||
[presentation.statusBucket],
|
[presentation.statusBucket],
|
||||||
@@ -829,6 +837,15 @@ function TabHandleContent({
|
|||||||
{presentation.label}
|
{presentation.label}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
{/* The dot is a laid-out sibling of the label, not an overlay, so a truncated label ends
|
||||||
|
before it instead of running underneath it. */}
|
||||||
|
{presentation.modified ? (
|
||||||
|
<View
|
||||||
|
style={styles.tabModifiedDot}
|
||||||
|
accessibilityLabel={t("workspace.tabs.modified")}
|
||||||
|
testID={modifiedTestId}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -868,7 +885,6 @@ function TabChip({
|
|||||||
onCloseTab: (tabId: string) => Promise<void> | void;
|
onCloseTab: (tabId: string) => Promise<void> | void;
|
||||||
dragHandleProps: DraggableListDragHandleProps | undefined;
|
dragHandleProps: DraggableListDragHandleProps | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab;
|
const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab;
|
||||||
const middleClickRef = useMiddleClickClose(
|
const middleClickRef = useMiddleClickClose(
|
||||||
useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]),
|
useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]),
|
||||||
@@ -982,16 +998,8 @@ function TabChip({
|
|||||||
backdrop={chipBackdrop}
|
backdrop={chipBackdrop}
|
||||||
tabLabelSkeletonStyle={tabLabelSkeletonStyle}
|
tabLabelSkeletonStyle={tabLabelSkeletonStyle}
|
||||||
tabLabelStyle={tabLabelStyle}
|
tabLabelStyle={tabLabelStyle}
|
||||||
|
modifiedTestId={`workspace-tab-modified-${buildDeterministicWorkspaceTabId(tab.target)}`}
|
||||||
/>
|
/>
|
||||||
{presentation.modified ? (
|
|
||||||
<View
|
|
||||||
style={styles.tabModifiedIndicator}
|
|
||||||
accessibilityLabel={t("workspace.tabs.modified")}
|
|
||||||
testID={`workspace-tab-modified-${buildDeterministicWorkspaceTabId(tab.target)}`}
|
|
||||||
>
|
|
||||||
<View style={styles.tabModifiedDot} />
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
@@ -1305,7 +1313,7 @@ function ResolvedWorkspaceDesktopTabsRow({
|
|||||||
tab.presentation.titleState === "loading"
|
tab.presentation.titleState === "loading"
|
||||||
? getFallbackTabLabel(tab.tab, fallbackTabLabels)
|
? getFallbackTabLabel(tab.tab, fallbackTabLabels)
|
||||||
: tab.presentation.label;
|
: tab.presentation.label;
|
||||||
return { key: tab.tab.key, label };
|
return { key: tab.tab.key, label, modified: tab.presentation.modified };
|
||||||
}),
|
}),
|
||||||
[fallbackTabLabels, tabs],
|
[fallbackTabLabels, tabs],
|
||||||
);
|
);
|
||||||
@@ -2009,17 +2017,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
tabModifiedIndicator: {
|
|
||||||
position: "absolute",
|
|
||||||
right: 8,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
tabModifiedDot: {
|
tabModifiedDot: {
|
||||||
width: 8,
|
width: TAB_MODIFIED_DOT_SIZE,
|
||||||
height: 8,
|
height: TAB_MODIFIED_DOT_SIZE,
|
||||||
|
flexShrink: 0,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
backgroundColor: theme.colors.foregroundMuted,
|
backgroundColor: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user